chiark / gitweb /
Add bfb extension when using BFB style GCode.
[cura.git] / Cura / gui / mainWindow.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2
3 import wx
4 import os
5 import webbrowser
6 import sys
7
8
9 from Cura.gui import configBase
10 from Cura.gui import expertConfig
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 simpleMode
17 from Cura.gui import sceneView
18 from Cura.gui import aboutWindow
19 from Cura.gui.util import dropTarget
20 #from Cura.gui.tools import batchRun
21 from Cura.gui.tools import pidDebugger
22 from Cura.gui.tools import minecraftImport
23 from Cura.util import profile
24 from Cura.util import version
25 import platform
26 from Cura.util import meshLoader
27
28 class mainWindow(wx.Frame):
29         def __init__(self):
30                 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
31
32                 wx.EVT_CLOSE(self, self.OnClose)
33
34                 # allow dropping any file, restrict later
35                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles))
36
37                 # TODO: wxWidgets 2.9.4 has a bug when NSView does not register for dragged types when wx drop target is set. It was fixed in 2.9.5
38                 if sys.platform.startswith('darwin'):
39                         try:
40                                 import objc
41                                 nswindow = objc.objc_object(c_void_p=self.MacGetTopLevelWindowRef())
42                                 view = nswindow.contentView()
43                                 view.registerForDraggedTypes_([u'NSFilenamesPboardType'])
44                         except:
45                                 pass
46
47                 self.normalModeOnlyItems = []
48
49                 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
50                 self.config = wx.FileConfig(appName="Cura",
51                                                 localFilename=mruFile,
52                                                 style=wx.CONFIG_USE_LOCAL_FILE)
53
54                 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)]
55                 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
56                 self.config.SetPath("/ModelMRU")
57                 self.modelFileHistory.Load(self.config)
58
59                 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)]
60                 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
61                 self.config.SetPath("/ProfileMRU")
62                 self.profileFileHistory.Load(self.config)
63
64                 self.menubar = wx.MenuBar()
65                 self.fileMenu = wx.Menu()
66                 i = self.fileMenu.Append(-1, _("Load model file...\tCTRL+L"))
67                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i)
68                 i = self.fileMenu.Append(-1, _("Save model...\tCTRL+S"))
69                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
70                 i = self.fileMenu.Append(-1, _("Reload platform\tF5"))
71                 self.Bind(wx.EVT_MENU, lambda e: self.scene.reloadScene(e), i)
72                 i = self.fileMenu.Append(-1, _("Clear platform"))
73                 self.Bind(wx.EVT_MENU, lambda e: self.scene.OnDeleteAll(e), i)
74
75                 self.fileMenu.AppendSeparator()
76                 i = self.fileMenu.Append(-1, _("Print...\tCTRL+P"))
77                 self.Bind(wx.EVT_MENU, lambda e: self.scene.OnPrintButton(1), i)
78                 i = self.fileMenu.Append(-1, _("Save GCode..."))
79                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
80                 i = self.fileMenu.Append(-1, _("Show slice engine log..."))
81                 self.Bind(wx.EVT_MENU, lambda e: self.scene._showEngineLog(), i)
82
83                 self.fileMenu.AppendSeparator()
84                 i = self.fileMenu.Append(-1, _("Open Profile..."))
85                 self.normalModeOnlyItems.append(i)
86                 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
87                 i = self.fileMenu.Append(-1, _("Save Profile..."))
88                 self.normalModeOnlyItems.append(i)
89                 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
90                 i = self.fileMenu.Append(-1, _("Load Profile from GCode..."))
91                 self.normalModeOnlyItems.append(i)
92                 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
93                 self.fileMenu.AppendSeparator()
94                 i = self.fileMenu.Append(-1, _("Reset Profile to default"))
95                 self.normalModeOnlyItems.append(i)
96                 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
97
98                 self.fileMenu.AppendSeparator()
99                 i = self.fileMenu.Append(-1, _("Preferences...\tCTRL+,"))
100                 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
101                 i = self.fileMenu.Append(-1, _("Machine settings..."))
102                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
103                 self.fileMenu.AppendSeparator()
104
105                 # Model MRU list
106                 modelHistoryMenu = wx.Menu()
107                 self.fileMenu.AppendMenu(wx.NewId(), '&' + _("Recent Model Files"), modelHistoryMenu)
108                 self.modelFileHistory.UseMenu(modelHistoryMenu)
109                 self.modelFileHistory.AddFilesToMenu()
110                 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
111
112                 # Profle MRU list
113                 profileHistoryMenu = wx.Menu()
114                 self.fileMenu.AppendMenu(wx.NewId(), _("Recent Profile Files"), profileHistoryMenu)
115                 self.profileFileHistory.UseMenu(profileHistoryMenu)
116                 self.profileFileHistory.AddFilesToMenu()
117                 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
118
119                 self.fileMenu.AppendSeparator()
120                 i = self.fileMenu.Append(wx.ID_EXIT, _("Quit"))
121                 self.Bind(wx.EVT_MENU, self.OnQuit, i)
122                 self.menubar.Append(self.fileMenu, '&' + _("File"))
123
124                 toolsMenu = wx.Menu()
125                 #i = toolsMenu.Append(-1, 'Batch run...')
126                 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
127                 #self.normalModeOnlyItems.append(i)
128
129                 if minecraftImport.hasMinecraft():
130                         i = toolsMenu.Append(-1, _("Minecraft map import..."))
131                         self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
132
133                 if version.isDevVersion():
134                         i = toolsMenu.Append(-1, _("PID Debugger..."))
135                         self.Bind(wx.EVT_MENU, self.OnPIDDebugger, i)
136
137                 i = toolsMenu.Append(-1, _("Copy profile to clipboard"))
138                 self.Bind(wx.EVT_MENU, self.onCopyProfileClipboard,i)
139
140                 toolsMenu.AppendSeparator()
141                 self.allAtOnceItem = toolsMenu.Append(-1, _("Print all at once"), kind=wx.ITEM_RADIO)
142                 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.allAtOnceItem)
143                 self.oneAtATime = toolsMenu.Append(-1, _("Print one at a time"), kind=wx.ITEM_RADIO)
144                 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.oneAtATime)
145                 if profile.getPreference('oneAtATime') == 'True':
146                         self.oneAtATime.Check(True)
147                 else:
148                         self.allAtOnceItem.Check(True)
149
150                 self.menubar.Append(toolsMenu, _("Tools"))
151
152                 #Machine menu for machine configuration/tooling
153                 self.machineMenu = wx.Menu()
154                 self.updateMachineMenu()
155
156                 self.menubar.Append(self.machineMenu, _("Machine"))
157
158                 expertMenu = wx.Menu()
159                 i = expertMenu.Append(-1, _("Switch to quickprint..."), kind=wx.ITEM_RADIO)
160                 self.switchToQuickprintMenuItem = i
161                 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
162
163                 i = expertMenu.Append(-1, _("Switch to full settings..."), kind=wx.ITEM_RADIO)
164                 self.switchToNormalMenuItem = i
165                 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
166                 expertMenu.AppendSeparator()
167
168                 i = expertMenu.Append(-1, _("Open expert settings...\tCTRL+E"))
169                 self.normalModeOnlyItems.append(i)
170                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
171                 expertMenu.AppendSeparator()
172                 i = expertMenu.Append(-1, _("Run first run wizard..."))
173                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
174                 self.bedLevelWizardMenuItem = expertMenu.Append(-1, _("Run bed leveling wizard..."))
175                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, self.bedLevelWizardMenuItem)
176                 self.headOffsetWizardMenuItem = expertMenu.Append(-1, _("Run head offset wizard..."))
177                 self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, self.headOffsetWizardMenuItem)
178
179                 self.menubar.Append(expertMenu, _("Expert"))
180
181                 helpMenu = wx.Menu()
182                 i = helpMenu.Append(-1, _("Online documentation..."))
183                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
184                 i = helpMenu.Append(-1, _("Report a problem..."))
185                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
186                 i = helpMenu.Append(-1, _("Check for update..."))
187                 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
188                 i = helpMenu.Append(-1, _("Open YouMagine website..."))
189                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://www.youmagine.com/'), i)
190                 i = helpMenu.Append(-1, _("About Cura..."))
191                 self.Bind(wx.EVT_MENU, self.OnAbout, i)
192                 self.menubar.Append(helpMenu, _("Help"))
193                 self.SetMenuBar(self.menubar)
194
195                 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
196                 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
197                 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
198                 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
199
200                 ##Gui components##
201                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
202                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
203
204                 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
205                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
206                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
207                 self.leftPane.SetSizer(self.leftSizer)
208
209                 #Preview window
210                 self.scene = sceneView.SceneView(self.rightPane)
211
212                 #Main sizer, to position the preview window, buttons and tab control
213                 sizer = wx.BoxSizer()
214                 self.rightPane.SetSizer(sizer)
215                 sizer.Add(self.scene, 1, flag=wx.EXPAND)
216
217                 # Main window sizer
218                 sizer = wx.BoxSizer(wx.VERTICAL)
219                 self.SetSizer(sizer)
220                 sizer.Add(self.splitter, 1, wx.EXPAND)
221                 sizer.Layout()
222                 self.sizer = sizer
223
224                 self.updateProfileToAllControls()
225
226                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
227
228                 self.simpleSettingsPanel.Show(False)
229                 self.normalSettingsPanel.Show(False)
230
231                 # Set default window size & position
232                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
233                 self.Centre()
234
235                 #Timer set; used to check if profile is on the clipboard
236                 self.timer = wx.Timer(self)
237                 self.Bind(wx.EVT_TIMER, self.onTimer)
238                 self.timer.Start(1000)
239                 self.lastTriedClipboard = profile.getProfileString()
240
241                 # Restore the window position, size & state from the preferences file
242                 try:
243                         if profile.getPreference('window_maximized') == 'True':
244                                 self.Maximize(True)
245                         else:
246                                 posx = int(profile.getPreference('window_pos_x'))
247                                 posy = int(profile.getPreference('window_pos_y'))
248                                 width = int(profile.getPreference('window_width'))
249                                 height = int(profile.getPreference('window_height'))
250                                 if posx > 0 or posy > 0:
251                                         self.SetPosition((posx,posy))
252                                 if width > 0 and height > 0:
253                                         self.SetSize((width,height))
254
255                         self.normalSashPos = int(profile.getPreference('window_normal_sash'))
256                 except:
257                         self.normalSashPos = 0
258                         self.Maximize(True)
259                 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
260                         self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
261
262                 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
263
264                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
265                         self.Centre()
266                 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
267                         self.Centre()
268                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
269                         self.SetSize((800,600))
270                         self.Centre()
271
272                 self.updateSliceMode()
273                 self.scene.SetFocus()
274
275         def onTimer(self, e):
276                 #Check if there is something in the clipboard
277                 profileString = ""
278                 try:
279                         if not wx.TheClipboard.IsOpened():
280                                 if not wx.TheClipboard.Open():
281                                         return
282                                 do = wx.TextDataObject()
283                                 if wx.TheClipboard.GetData(do):
284                                         profileString = do.GetText()
285                                 wx.TheClipboard.Close()
286
287                                 startTag = "CURA_PROFILE_STRING:"
288                                 if startTag in profileString:
289                                         #print "Found correct syntax on clipboard"
290                                         profileString = profileString.replace("\n","").strip()
291                                         profileString = profileString[profileString.find(startTag)+len(startTag):]
292                                         if profileString != self.lastTriedClipboard:
293                                                 print profileString
294                                                 self.lastTriedClipboard = profileString
295                                                 profile.setProfileFromString(profileString)
296                                                 self.scene.notification.message("Loaded new profile from clipboard.")
297                                                 self.updateProfileToAllControls()
298                 except:
299                         print "Unable to read from clipboard"
300
301
302         def updateSliceMode(self):
303                 isSimple = profile.getPreference('startMode') == 'Simple'
304
305                 self.normalSettingsPanel.Show(not isSimple)
306                 self.simpleSettingsPanel.Show(isSimple)
307                 self.leftPane.Layout()
308
309                 for i in self.normalModeOnlyItems:
310                         i.Enable(not isSimple)
311                 if isSimple:
312                         self.switchToQuickprintMenuItem.Check()
313                 else:
314                         self.switchToNormalMenuItem.Check()
315
316                 # Set splitter sash position & size
317                 if isSimple:
318                         # Save normal mode sash
319                         self.normalSashPos = self.splitter.GetSashPosition()
320
321                         # Change location of sash to width of quick mode pane
322                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
323                         self.splitter.SetSashPosition(width, True)
324
325                         # Disable sash
326                         self.splitter.SetSashSize(0)
327                 else:
328                         self.splitter.SetSashPosition(self.normalSashPos, True)
329                         # Enabled sash
330                         self.splitter.SetSashSize(4)
331                 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
332                 if profile.getMachineSetting('machine_type') == 'ultimaker2':
333                         self.bedLevelWizardMenuItem.Enable(False)
334                         self.headOffsetWizardMenuItem.Enable(False)
335                 if int(profile.getMachineSetting('extruder_amount')) < 2:
336                         self.headOffsetWizardMenuItem.Enable(False)
337                 self.scene.updateProfileToControls()
338                 self.scene._scene.pushFree()
339
340         def onOneAtATimeSwitch(self, e):
341                 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
342                 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
343                         wx.MessageBox(_('For "One at a time" printing, you need to have entered the correct head size and gantry height in the machine settings'), _('One at a time warning'), wx.OK | wx.ICON_WARNING)
344                 self.scene.updateProfileToControls()
345                 self.scene._scene.pushFree()
346                 self.scene.sceneUpdated()
347
348         def OnPreferences(self, e):
349                 prefDialog = preferencesDialog.preferencesDialog(self)
350                 prefDialog.Centre()
351                 prefDialog.Show()
352                 prefDialog.Raise()
353                 wx.CallAfter(prefDialog.Show)
354
355         def OnMachineSettings(self, e):
356                 prefDialog = preferencesDialog.machineSettingsDialog(self)
357                 prefDialog.Centre()
358                 prefDialog.Show()
359                 prefDialog.Raise()
360
361         def OnDropFiles(self, files):
362                 if len(files) > 0:
363                         self.updateProfileToAllControls()
364                 self.scene.loadFiles(files)
365
366         def OnModelMRU(self, e):
367                 fileNum = e.GetId() - self.ID_MRU_MODEL1
368                 path = self.modelFileHistory.GetHistoryFile(fileNum)
369                 # Update Model MRU
370                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
371                 self.config.SetPath("/ModelMRU")
372                 self.modelFileHistory.Save(self.config)
373                 self.config.Flush()
374                 # Load Model
375                 profile.putPreference('lastFile', path)
376                 filelist = [ path ]
377                 self.scene.loadFiles(filelist)
378
379         def addToModelMRU(self, file):
380                 self.modelFileHistory.AddFileToHistory(file)
381                 self.config.SetPath("/ModelMRU")
382                 self.modelFileHistory.Save(self.config)
383                 self.config.Flush()
384
385         def OnProfileMRU(self, e):
386                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
387                 path = self.profileFileHistory.GetHistoryFile(fileNum)
388                 # Update Profile MRU
389                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
390                 self.config.SetPath("/ProfileMRU")
391                 self.profileFileHistory.Save(self.config)
392                 self.config.Flush()
393                 # Load Profile
394                 profile.loadProfile(path)
395                 self.updateProfileToAllControls()
396
397         def addToProfileMRU(self, file):
398                 self.profileFileHistory.AddFileToHistory(file)
399                 self.config.SetPath("/ProfileMRU")
400                 self.profileFileHistory.Save(self.config)
401                 self.config.Flush()
402
403         def updateProfileToAllControls(self):
404                 self.scene.updateProfileToControls()
405                 self.normalSettingsPanel.updateProfileToControls()
406                 self.simpleSettingsPanel.updateProfileToControls()
407
408         def reloadSettingPanels(self):
409                 self.leftSizer.Detach(self.simpleSettingsPanel)
410                 self.leftSizer.Detach(self.normalSettingsPanel)
411                 self.simpleSettingsPanel.Destroy()
412                 self.normalSettingsPanel.Destroy()
413                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
414                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
415                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
416                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
417                 self.updateSliceMode()
418                 self.updateProfileToAllControls()
419
420         def updateMachineMenu(self):
421                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
422                 for item in self.machineMenu.GetMenuItems():
423                         self.machineMenu.RemoveItem(item)
424
425                 #Add a menu item for each machine configuration.
426                 for n in xrange(0, profile.getMachineCount()):
427                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
428                         if n == int(profile.getPreferenceFloat('active_machine')):
429                                 i.Check(True)
430                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
431
432                 self.machineMenu.AppendSeparator()
433
434                 i = self.machineMenu.Append(-1, _("Machine settings..."))
435                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
436
437                 #Add tools for machines.
438                 self.machineMenu.AppendSeparator()
439
440                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
441                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
442
443                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
444                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
445
446         def OnLoadProfile(self, e):
447                 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)
448                 dlg.SetWildcard("ini files (*.ini)|*.ini")
449                 if dlg.ShowModal() == wx.ID_OK:
450                         profileFile = dlg.GetPath()
451                         profile.loadProfile(profileFile)
452                         self.updateProfileToAllControls()
453
454                         # Update the Profile MRU
455                         self.addToProfileMRU(profileFile)
456                 dlg.Destroy()
457
458         def OnLoadProfileFromGcode(self, e):
459                 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)
460                 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
461                 if dlg.ShowModal() == wx.ID_OK:
462                         gcodeFile = dlg.GetPath()
463                         f = open(gcodeFile, 'r')
464                         hasProfile = False
465                         for line in f:
466                                 if line.startswith(';CURA_PROFILE_STRING:'):
467                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
468                                         hasProfile = True
469                         if hasProfile:
470                                 self.updateProfileToAllControls()
471                         else:
472                                 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)
473                 dlg.Destroy()
474
475         def OnSaveProfile(self, e):
476                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
477                 dlg.SetWildcard("ini files (*.ini)|*.ini")
478                 if dlg.ShowModal() == wx.ID_OK:
479                         profileFile = dlg.GetPath()
480                         if platform.system() == 'Linux': #hack for linux, as for some reason the .ini is not appended.
481                                 profileFile += '.ini'
482                         profile.saveProfile(profileFile)
483                 dlg.Destroy()
484
485         def OnResetProfile(self, e):
486                 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)
487                 result = dlg.ShowModal() == wx.ID_YES
488                 dlg.Destroy()
489                 if result:
490                         profile.resetProfile()
491                         self.updateProfileToAllControls()
492
493         def OnSimpleSwitch(self, e):
494                 profile.putPreference('startMode', 'Simple')
495                 self.updateSliceMode()
496
497         def OnNormalSwitch(self, e):
498                 profile.putPreference('startMode', 'Normal')
499                 self.updateSliceMode()
500
501         def OnDefaultMarlinFirmware(self, e):
502                 firmwareInstall.InstallFirmware()
503
504         def OnCustomFirmware(self, e):
505                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
506                         wx.MessageBox(_("Warning: Installing a custom firmware does not guarantee that you machine will function correctly, and could damage your machine."), _("Firmware update"), wx.OK | wx.ICON_EXCLAMATION)
507                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
508                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
509                 if dlg.ShowModal() == wx.ID_OK:
510                         filename = dlg.GetPath()
511                         if not(os.path.exists(filename)):
512                                 return
513                         #For some reason my Ubuntu 10.10 crashes here.
514                         firmwareInstall.InstallFirmware(filename)
515
516         def OnFirstRunWizard(self, e):
517                 self.Hide()
518                 configWizard.configWizard()
519                 self.Show()
520                 self.reloadSettingPanels()
521
522         def OnSelectMachine(self, index):
523                 profile.setActiveMachine(index)
524                 self.reloadSettingPanels()
525
526         def OnBedLevelWizard(self, e):
527                 configWizard.bedLevelWizard()
528
529         def OnHeadOffsetWizard(self, e):
530                 configWizard.headOffsetWizard()
531
532         def OnExpertOpen(self, e):
533                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
534                 ecw.Centre()
535                 ecw.Show()
536
537         def OnMinecraftImport(self, e):
538                 mi = minecraftImport.minecraftImportWindow(self)
539                 mi.Centre()
540                 mi.Show(True)
541
542         def OnPIDDebugger(self, e):
543                 debugger = pidDebugger.debuggerWindow(self)
544                 debugger.Centre()
545                 debugger.Show(True)
546
547         def onCopyProfileClipboard(self, e):
548                 try:
549                         if not wx.TheClipboard.IsOpened():
550                                 wx.TheClipboard.Open()
551                                 clipData = wx.TextDataObject()
552                                 self.lastTriedClipboard = profile.getProfileString()
553                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
554                                 clipData.SetText(profileString)
555                                 wx.TheClipboard.SetData(clipData)
556                                 wx.TheClipboard.Close()
557                 except:
558                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
559
560         def OnCheckForUpdate(self, e):
561                 newVersion = version.checkForNewerVersion()
562                 if newVersion is not None:
563                         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:
564                                 webbrowser.open(newVersion)
565                 else:
566                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
567
568         def OnAbout(self, e):
569                 aboutBox = aboutWindow.aboutWindow()
570                 aboutBox.Centre()
571                 aboutBox.Show()
572
573         def OnClose(self, e):
574                 profile.saveProfile(profile.getDefaultProfilePath(), True)
575
576                 # Save the window position, size & state from the preferences file
577                 profile.putPreference('window_maximized', self.IsMaximized())
578                 if not self.IsMaximized() and not self.IsIconized():
579                         (posx, posy) = self.GetPosition()
580                         profile.putPreference('window_pos_x', posx)
581                         profile.putPreference('window_pos_y', posy)
582                         (width, height) = self.GetSize()
583                         profile.putPreference('window_width', width)
584                         profile.putPreference('window_height', height)
585
586                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
587                         isSimple = profile.getPreference('startMode') == 'Simple'
588                         if not isSimple:
589                                 self.normalSashPos = self.splitter.GetSashPosition()
590                         profile.putPreference('window_normal_sash', self.normalSashPos)
591
592                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
593                 print "Closing down"
594                 self.scene.OnPaint = lambda e : e
595                 self.scene._engine.cleanup()
596                 self.Destroy()
597
598         def OnQuit(self, e):
599                 self.Close()
600
601 class normalSettingsPanel(configBase.configPanelBase):
602         "Main user interface window"
603         def __init__(self, parent, callback = None):
604                 super(normalSettingsPanel, self).__init__(parent, callback)
605
606                 #Main tabs
607                 self.nb = wx.Notebook(self)
608                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
609                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
610
611                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
612                 self._addSettingsToPanels('basic', left, right)
613                 self.SizeLabelWidths(left, right)
614
615                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
616                 self._addSettingsToPanels('advanced', left, right)
617                 self.SizeLabelWidths(left, right)
618
619                 #Plugin page
620                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
621                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
622
623                 #Alteration page
624                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
625                         self.alterationPanel = None
626                 else:
627                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
628                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
629
630                 self.Bind(wx.EVT_SIZE, self.OnSize)
631
632                 self.nb.SetSize(self.GetSize())
633                 self.UpdateSize(self.printPanel)
634                 self.UpdateSize(self.advancedPanel)
635
636         def _addSettingsToPanels(self, category, left, right):
637                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
638
639                 p = left
640                 n = 0
641                 for title in profile.getSubCategoriesFor(category):
642                         n += 1 + len(profile.getSettingsForCategory(category, title))
643                         if n > count / 2:
644                                 p = right
645                         configBase.TitleRow(p, _(title))
646                         for s in profile.getSettingsForCategory(category, title):
647                                 configBase.SettingRow(p, s.getName())
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 max(colSize1[0], colSize2[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                 if self.alterationPanel is not None:
714                         self.alterationPanel.updateProfileToControls()
715                 self.pluginPanel.updateProfileToControls()