chiark / gitweb /
Add debug feature to watch and auto install firmware to easy firmware development.
[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                         i = toolsMenu.Append(-1, _("Auto Firmware Update..."))
137                         self.Bind(wx.EVT_MENU, self.OnAutoFirmwareUpdate, i)
138
139                 i = toolsMenu.Append(-1, _("Copy profile to clipboard"))
140                 self.Bind(wx.EVT_MENU, self.onCopyProfileClipboard,i)
141
142                 toolsMenu.AppendSeparator()
143                 self.allAtOnceItem = toolsMenu.Append(-1, _("Print all at once"), kind=wx.ITEM_RADIO)
144                 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.allAtOnceItem)
145                 self.oneAtATime = toolsMenu.Append(-1, _("Print one at a time"), kind=wx.ITEM_RADIO)
146                 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.oneAtATime)
147                 if profile.getPreference('oneAtATime') == 'True':
148                         self.oneAtATime.Check(True)
149                 else:
150                         self.allAtOnceItem.Check(True)
151
152                 self.menubar.Append(toolsMenu, _("Tools"))
153
154                 #Machine menu for machine configuration/tooling
155                 self.machineMenu = wx.Menu()
156                 self.updateMachineMenu()
157
158                 self.menubar.Append(self.machineMenu, _("Machine"))
159
160                 expertMenu = wx.Menu()
161                 i = expertMenu.Append(-1, _("Switch to quickprint..."), kind=wx.ITEM_RADIO)
162                 self.switchToQuickprintMenuItem = i
163                 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
164
165                 i = expertMenu.Append(-1, _("Switch to full settings..."), kind=wx.ITEM_RADIO)
166                 self.switchToNormalMenuItem = i
167                 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
168                 expertMenu.AppendSeparator()
169
170                 i = expertMenu.Append(-1, _("Open expert settings...\tCTRL+E"))
171                 self.normalModeOnlyItems.append(i)
172                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
173                 expertMenu.AppendSeparator()
174                 i = expertMenu.Append(-1, _("Run first run wizard..."))
175                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
176                 self.bedLevelWizardMenuItem = expertMenu.Append(-1, _("Run bed leveling wizard..."))
177                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, self.bedLevelWizardMenuItem)
178                 self.headOffsetWizardMenuItem = expertMenu.Append(-1, _("Run head offset wizard..."))
179                 self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, self.headOffsetWizardMenuItem)
180
181                 self.menubar.Append(expertMenu, _("Expert"))
182
183                 helpMenu = wx.Menu()
184                 i = helpMenu.Append(-1, _("Online documentation..."))
185                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://lulzbot.com/cura'), i)
186                 i = helpMenu.Append(-1, _("Report a problem..."))
187                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/alephobjects/Cura/issues'), i)
188                 #i = helpMenu.Append(-1, _("Check for update..."))
189                 #self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
190                 #i = helpMenu.Append(-1, _("Open YouMagine website..."))
191                 #self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://www.youmagine.com/'), i)
192                 i = helpMenu.Append(-1, _("About Cura..."))
193                 self.Bind(wx.EVT_MENU, self.OnAbout, i)
194                 self.menubar.Append(helpMenu, _("Help"))
195                 self.SetMenuBar(self.menubar)
196
197                 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
198                 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
199                 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
200                 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
201
202                 ##Gui components##
203                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
204                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
205
206                 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
207                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
208                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
209                 self.leftPane.SetSizer(self.leftSizer)
210
211                 #Preview window
212                 self.scene = sceneView.SceneView(self.rightPane)
213
214                 #Main sizer, to position the preview window, buttons and tab control
215                 sizer = wx.BoxSizer()
216                 self.rightPane.SetSizer(sizer)
217                 sizer.Add(self.scene, 1, flag=wx.EXPAND)
218
219                 # Main window sizer
220                 sizer = wx.BoxSizer(wx.VERTICAL)
221                 self.SetSizer(sizer)
222                 sizer.Add(self.splitter, 1, wx.EXPAND)
223                 sizer.Layout()
224                 self.sizer = sizer
225
226                 self.updateProfileToAllControls()
227
228                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
229
230                 self.simpleSettingsPanel.Show(False)
231                 self.normalSettingsPanel.Show(False)
232
233                 # Set default window size & position
234                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
235                 self.Centre()
236
237                 #Timer set; used to check if profile is on the clipboard
238                 self.timer = wx.Timer(self)
239                 self.Bind(wx.EVT_TIMER, self.onTimer)
240                 self.timer.Start(1000)
241                 self.lastTriedClipboard = profile.getProfileString()
242
243                 # Restore the window position, size & state from the preferences file
244                 try:
245                         if profile.getPreference('window_maximized') == 'True':
246                                 self.Maximize(True)
247                         else:
248                                 posx = int(profile.getPreference('window_pos_x'))
249                                 posy = int(profile.getPreference('window_pos_y'))
250                                 width = int(profile.getPreference('window_width'))
251                                 height = int(profile.getPreference('window_height'))
252                                 if posx > 0 or posy > 0:
253                                         self.SetPosition((posx,posy))
254                                 if width > 0 and height > 0:
255                                         self.SetSize((width,height))
256
257                         self.normalSashPos = int(profile.getPreference('window_normal_sash'))
258                 except:
259                         self.normalSashPos = 0
260                         self.Maximize(True)
261                 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
262                         self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
263
264                 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
265
266                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
267                         self.Centre()
268                 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
269                         self.Centre()
270                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
271                         self.SetSize((800,600))
272                         self.Centre()
273
274                 self.updateSliceMode()
275                 self.scene.SetFocus()
276
277         def onTimer(self, e):
278                 #Check if there is something in the clipboard
279                 profileString = ""
280                 try:
281                         if not wx.TheClipboard.IsOpened():
282                                 if not wx.TheClipboard.Open():
283                                         return
284                                 do = wx.TextDataObject()
285                                 if wx.TheClipboard.GetData(do):
286                                         profileString = do.GetText()
287                                 wx.TheClipboard.Close()
288
289                                 startTag = "CURA_PROFILE_STRING:"
290                                 if startTag in profileString:
291                                         #print "Found correct syntax on clipboard"
292                                         profileString = profileString.replace("\n","").strip()
293                                         profileString = profileString[profileString.find(startTag)+len(startTag):]
294                                         if profileString != self.lastTriedClipboard:
295                                                 print profileString
296                                                 self.lastTriedClipboard = profileString
297                                                 profile.setProfileFromString(profileString)
298                                                 self.scene.notification.message("Loaded new profile from clipboard.")
299                                                 self.updateProfileToAllControls()
300                 except:
301                         print "Unable to read from clipboard"
302
303
304         def updateSliceMode(self):
305                 isSimple = profile.getPreference('startMode') == 'Simple'
306
307                 self.normalSettingsPanel.Show(not isSimple)
308                 self.simpleSettingsPanel.Show(isSimple)
309                 self.leftPane.Layout()
310
311                 for i in self.normalModeOnlyItems:
312                         i.Enable(not isSimple)
313                 if isSimple:
314                         self.switchToQuickprintMenuItem.Check()
315                 else:
316                         self.switchToNormalMenuItem.Check()
317
318                 # Set splitter sash position & size
319                 if isSimple:
320                         # Save normal mode sash
321                         self.normalSashPos = self.splitter.GetSashPosition()
322
323                         # Change location of sash to width of quick mode pane
324                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
325                         self.splitter.SetSashPosition(width, True)
326
327                         # Disable sash
328                         self.splitter.SetSashSize(0)
329                 else:
330                         self.splitter.SetSashPosition(self.normalSashPos, True)
331                         # Enabled sash
332                         self.splitter.SetSashSize(4)
333                 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
334                 if profile.getMachineSetting('machine_type') == 'ultimaker2' or profile.getMachineSetting('machine_type') == 'lulzbot_mini' or profile.getMachineSetting('machine_type') == 'lulzbot_TAZ':
335                         self.bedLevelWizardMenuItem.Enable(False)
336                         self.headOffsetWizardMenuItem.Enable(False)
337                 else:
338                         self.bedLevelWizardMenuItem.Enable(True)
339                         self.headOffsetWizardMenuItem.Enable(False)
340                 if int(profile.getMachineSetting('extruder_amount')) < 2:
341                         self.headOffsetWizardMenuItem.Enable(False)
342                 self.scene.updateProfileToControls()
343                 self.scene._scene.pushFree()
344
345         def onOneAtATimeSwitch(self, e):
346                 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
347                 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
348                         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)
349                 self.scene.updateProfileToControls()
350                 self.scene._scene.pushFree()
351                 self.scene.sceneUpdated()
352
353         def OnPreferences(self, e):
354                 prefDialog = preferencesDialog.preferencesDialog(self)
355                 prefDialog.Centre()
356                 prefDialog.Show()
357                 prefDialog.Raise()
358                 wx.CallAfter(prefDialog.Show)
359
360         def OnMachineSettings(self, e):
361                 prefDialog = preferencesDialog.machineSettingsDialog(self)
362                 prefDialog.Centre()
363                 prefDialog.Show()
364                 prefDialog.Raise()
365
366         def OnDropFiles(self, files):
367                 if len(files) > 0:
368                         self.updateProfileToAllControls()
369                 self.scene.loadFiles(files)
370
371         def OnModelMRU(self, e):
372                 fileNum = e.GetId() - self.ID_MRU_MODEL1
373                 path = self.modelFileHistory.GetHistoryFile(fileNum)
374                 # Update Model MRU
375                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
376                 self.config.SetPath("/ModelMRU")
377                 self.modelFileHistory.Save(self.config)
378                 self.config.Flush()
379                 # Load Model
380                 profile.putPreference('lastFile', path)
381                 filelist = [ path ]
382                 self.scene.loadFiles(filelist)
383
384         def addToModelMRU(self, file):
385                 self.modelFileHistory.AddFileToHistory(file)
386                 self.config.SetPath("/ModelMRU")
387                 self.modelFileHistory.Save(self.config)
388                 self.config.Flush()
389
390         def OnProfileMRU(self, e):
391                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
392                 path = self.profileFileHistory.GetHistoryFile(fileNum)
393                 # Update Profile MRU
394                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
395                 self.config.SetPath("/ProfileMRU")
396                 self.profileFileHistory.Save(self.config)
397                 self.config.Flush()
398                 # Load Profile
399                 profile.loadProfile(path)
400                 self.updateProfileToAllControls()
401
402         def addToProfileMRU(self, file):
403                 self.profileFileHistory.AddFileToHistory(file)
404                 self.config.SetPath("/ProfileMRU")
405                 self.profileFileHistory.Save(self.config)
406                 self.config.Flush()
407
408         def updateProfileToAllControls(self):
409                 self.scene.updateProfileToControls()
410                 self.normalSettingsPanel.updateProfileToControls()
411                 self.simpleSettingsPanel.updateProfileToControls()
412
413         def reloadSettingPanels(self):
414                 self.leftSizer.Detach(self.simpleSettingsPanel)
415                 self.leftSizer.Detach(self.normalSettingsPanel)
416                 self.simpleSettingsPanel.Destroy()
417                 self.normalSettingsPanel.Destroy()
418                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
419                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
420                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
421                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
422                 self.updateSliceMode()
423                 self.updateProfileToAllControls()
424
425         def updateMachineMenu(self):
426                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
427                 for item in self.machineMenu.GetMenuItems():
428                         self.machineMenu.RemoveItem(item)
429
430                 #Add a menu item for each machine configuration.
431                 for n in xrange(0, profile.getMachineCount()):
432                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
433                         if n == int(profile.getPreferenceFloat('active_machine')):
434                                 i.Check(True)
435                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
436
437                 self.machineMenu.AppendSeparator()
438
439                 i = self.machineMenu.Append(-1, _("Machine settings..."))
440                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
441
442                 #Add tools for machines.
443                 self.machineMenu.AppendSeparator()
444
445                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
446                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
447
448                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
449                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
450
451         def OnLoadProfile(self, e):
452                 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)
453                 dlg.SetWildcard("ini files (*.ini)|*.ini")
454                 if dlg.ShowModal() == wx.ID_OK:
455                         profileFile = dlg.GetPath()
456                         profile.loadProfile(profileFile)
457                         self.updateProfileToAllControls()
458
459                         # Update the Profile MRU
460                         self.addToProfileMRU(profileFile)
461                 dlg.Destroy()
462
463         def OnLoadProfileFromGcode(self, e):
464                 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)
465                 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
466                 if dlg.ShowModal() == wx.ID_OK:
467                         gcodeFile = dlg.GetPath()
468                         f = open(gcodeFile, 'r')
469                         hasProfile = False
470                         for line in f:
471                                 if line.startswith(';CURA_PROFILE_STRING:'):
472                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
473                                         if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
474                                                 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
475                                         hasProfile = True
476                         if hasProfile:
477                                 self.updateProfileToAllControls()
478                         else:
479                                 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)
480                 dlg.Destroy()
481
482         def OnSaveProfile(self, e):
483                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
484                 dlg.SetWildcard("ini files (*.ini)|*.ini")
485                 if dlg.ShowModal() == wx.ID_OK:
486                         profileFile = dlg.GetPath()
487                         if not profileFile.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
488                                 profileFile += '.ini'
489                         profile.saveProfile(profileFile)
490                 dlg.Destroy()
491
492         def OnResetProfile(self, e):
493                 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)
494                 result = dlg.ShowModal() == wx.ID_YES
495                 dlg.Destroy()
496                 if result:
497                         profile.resetProfile()
498                         self.updateProfileToAllControls()
499
500         def OnSimpleSwitch(self, e):
501                 profile.putPreference('startMode', 'Simple')
502                 self.updateSliceMode()
503
504         def OnNormalSwitch(self, e):
505                 profile.putPreference('startMode', 'Normal')
506                 self.updateSliceMode()
507
508         def OnDefaultMarlinFirmware(self, e):
509                 firmwareInstall.InstallFirmware(self)
510
511         def OnCustomFirmware(self, e):
512                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
513                         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)
514                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
515                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
516                 if dlg.ShowModal() == wx.ID_OK:
517                         filename = dlg.GetPath()
518                         dlg.Destroy()
519                         if not(os.path.exists(filename)):
520                                 return
521                         #For some reason my Ubuntu 10.10 crashes here.
522                         firmwareInstall.InstallFirmware(self, filename)
523
524         def OnFirstRunWizard(self, e):
525                 self.Hide()
526                 configWizard.configWizard()
527                 self.Show()
528                 self.reloadSettingPanels()
529
530         def OnSelectMachine(self, index):
531                 profile.setActiveMachine(index)
532                 self.reloadSettingPanels()
533
534         def OnBedLevelWizard(self, e):
535                 configWizard.bedLevelWizard()
536
537         def OnHeadOffsetWizard(self, e):
538                 configWizard.headOffsetWizard()
539
540         def OnExpertOpen(self, e):
541                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
542                 ecw.Centre()
543                 ecw.Show()
544
545         def OnMinecraftImport(self, e):
546                 mi = minecraftImport.minecraftImportWindow(self)
547                 mi.Centre()
548                 mi.Show(True)
549
550         def OnPIDDebugger(self, e):
551                 debugger = pidDebugger.debuggerWindow(self)
552                 debugger.Centre()
553                 debugger.Show(True)
554
555         def OnAutoFirmwareUpdate(self, e):
556                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
557                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
558                 if dlg.ShowModal() == wx.ID_OK:
559                         filename = dlg.GetPath()
560                         dlg.Destroy()
561                         if not(os.path.exists(filename)):
562                                 return
563                         #For some reason my Ubuntu 10.10 crashes here.
564                         installer = firmwareInstall.AutoUpdateFirmware(self, filename)
565
566         def onCopyProfileClipboard(self, e):
567                 try:
568                         if not wx.TheClipboard.IsOpened():
569                                 wx.TheClipboard.Open()
570                                 clipData = wx.TextDataObject()
571                                 self.lastTriedClipboard = profile.getProfileString()
572                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
573                                 clipData.SetText(profileString)
574                                 wx.TheClipboard.SetData(clipData)
575                                 wx.TheClipboard.Close()
576                 except:
577                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
578
579         def OnCheckForUpdate(self, e):
580                 newVersion = version.checkForNewerVersion()
581                 if newVersion is not None:
582                         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:
583                                 webbrowser.open(newVersion)
584                 else:
585                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
586
587         def OnAbout(self, e):
588                 aboutBox = aboutWindow.aboutWindow()
589                 aboutBox.Centre()
590                 aboutBox.Show()
591
592         def OnClose(self, e):
593                 profile.saveProfile(profile.getDefaultProfilePath(), True)
594
595                 # Save the window position, size & state from the preferences file
596                 profile.putPreference('window_maximized', self.IsMaximized())
597                 if not self.IsMaximized() and not self.IsIconized():
598                         (posx, posy) = self.GetPosition()
599                         profile.putPreference('window_pos_x', posx)
600                         profile.putPreference('window_pos_y', posy)
601                         (width, height) = self.GetSize()
602                         profile.putPreference('window_width', width)
603                         profile.putPreference('window_height', height)
604
605                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
606                         isSimple = profile.getPreference('startMode') == 'Simple'
607                         if not isSimple:
608                                 self.normalSashPos = self.splitter.GetSashPosition()
609                         profile.putPreference('window_normal_sash', self.normalSashPos)
610
611                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
612                 print "Closing down"
613                 self.scene.OnPaint = lambda e : e
614                 self.scene._engine.cleanup()
615                 self.Destroy()
616
617         def OnQuit(self, e):
618                 self.Close()
619
620 class normalSettingsPanel(configBase.configPanelBase):
621         "Main user interface window"
622         def __init__(self, parent, callback = None):
623                 super(normalSettingsPanel, self).__init__(parent, callback)
624
625                 #Main tabs
626                 self.nb = wx.Notebook(self)
627                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
628                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
629
630                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
631                 self._addSettingsToPanels('basic', left, right)
632                 self.SizeLabelWidths(left, right)
633
634                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
635                 self._addSettingsToPanels('advanced', left, right)
636                 self.SizeLabelWidths(left, right)
637
638                 #Plugin page
639                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
640                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
641
642                 #Alteration page
643                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
644                         self.alterationPanel = None
645                 else:
646                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
647                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
648
649                 self.Bind(wx.EVT_SIZE, self.OnSize)
650
651                 self.nb.SetSize(self.GetSize())
652                 self.UpdateSize(self.printPanel)
653                 self.UpdateSize(self.advancedPanel)
654
655         def _addSettingsToPanels(self, category, left, right):
656                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
657
658                 p = left
659                 n = 0
660                 for title in profile.getSubCategoriesFor(category):
661                         n += 1 + len(profile.getSettingsForCategory(category, title))
662                         if n > count / 2:
663                                 p = right
664                         configBase.TitleRow(p, _(title))
665                         for s in profile.getSettingsForCategory(category, title):
666                                 configBase.SettingRow(p, s.getName())
667
668         def SizeLabelWidths(self, left, right):
669                 leftWidth = self.getLabelColumnWidth(left)
670                 rightWidth = self.getLabelColumnWidth(right)
671                 maxWidth = max(leftWidth, rightWidth)
672                 self.setLabelColumnWidth(left, maxWidth)
673                 self.setLabelColumnWidth(right, maxWidth)
674
675         def OnSize(self, e):
676                 # Make the size of the Notebook control the same size as this control
677                 self.nb.SetSize(self.GetSize())
678
679                 # Propegate the OnSize() event (just in case)
680                 e.Skip()
681
682                 # Perform out resize magic
683                 self.UpdateSize(self.printPanel)
684                 self.UpdateSize(self.advancedPanel)
685
686         def UpdateSize(self, configPanel):
687                 sizer = configPanel.GetSizer()
688
689                 # Pseudocde
690                 # if horizontal:
691                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
692                 #         switch to vertical
693                 # else:
694                 #     if width(col1) > (best_width(col1) + best_width(col1)):
695                 #         switch to horizontal
696                 #
697
698                 col1 = configPanel.leftPanel
699                 colSize1 = col1.GetSize()
700                 colBestSize1 = col1.GetBestSize()
701                 col2 = configPanel.rightPanel
702                 colSize2 = col2.GetSize()
703                 colBestSize2 = col2.GetBestSize()
704
705                 orientation = sizer.GetOrientation()
706
707                 if orientation == wx.HORIZONTAL:
708                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
709                                 configPanel.Freeze()
710                                 sizer = wx.BoxSizer(wx.VERTICAL)
711                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
712                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
713                                 configPanel.SetSizer(sizer)
714                                 #sizer.Layout()
715                                 configPanel.Layout()
716                                 self.Layout()
717                                 configPanel.Thaw()
718                 else:
719                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
720                                 configPanel.Freeze()
721                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
722                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
723                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
724                                 configPanel.SetSizer(sizer)
725                                 #sizer.Layout()
726                                 configPanel.Layout()
727                                 self.Layout()
728                                 configPanel.Thaw()
729
730         def updateProfileToControls(self):
731                 super(normalSettingsPanel, self).updateProfileToControls()
732                 if self.alterationPanel is not None:
733                         self.alterationPanel.updateProfileToControls()
734                 self.pluginPanel.updateProfileToControls()