chiark / gitweb /
c4bbe4cbae599c5536c0fcbda6be8e9f7179da7a
[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://lulzbot.com/cura'), i)
184                 i = helpMenu.Append(-1, _("Report a problem..."))
185                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/alephobjects/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' or profile.getMachineSetting('machine_type') == 'lulzbot_mini' or profile.getMachineSetting('machine_type') == 'lulzbot_TAZ':
333                         self.bedLevelWizardMenuItem.Enable(False)
334                         self.headOffsetWizardMenuItem.Enable(False)
335                 else:
336                         self.bedLevelWizardMenuItem.Enable(True)
337                         self.headOffsetWizardMenuItem.Enable(False)
338                 if int(profile.getMachineSetting('extruder_amount')) < 2:
339                         self.headOffsetWizardMenuItem.Enable(False)
340                 self.scene.updateProfileToControls()
341                 self.scene._scene.pushFree()
342
343         def onOneAtATimeSwitch(self, e):
344                 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
345                 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
346                         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)
347                 self.scene.updateProfileToControls()
348                 self.scene._scene.pushFree()
349                 self.scene.sceneUpdated()
350
351         def OnPreferences(self, e):
352                 prefDialog = preferencesDialog.preferencesDialog(self)
353                 prefDialog.Centre()
354                 prefDialog.Show()
355                 prefDialog.Raise()
356                 wx.CallAfter(prefDialog.Show)
357
358         def OnMachineSettings(self, e):
359                 prefDialog = preferencesDialog.machineSettingsDialog(self)
360                 prefDialog.Centre()
361                 prefDialog.Show()
362                 prefDialog.Raise()
363
364         def OnDropFiles(self, files):
365                 if len(files) > 0:
366                         self.updateProfileToAllControls()
367                 self.scene.loadFiles(files)
368
369         def OnModelMRU(self, e):
370                 fileNum = e.GetId() - self.ID_MRU_MODEL1
371                 path = self.modelFileHistory.GetHistoryFile(fileNum)
372                 # Update Model MRU
373                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
374                 self.config.SetPath("/ModelMRU")
375                 self.modelFileHistory.Save(self.config)
376                 self.config.Flush()
377                 # Load Model
378                 profile.putPreference('lastFile', path)
379                 filelist = [ path ]
380                 self.scene.loadFiles(filelist)
381
382         def addToModelMRU(self, file):
383                 self.modelFileHistory.AddFileToHistory(file)
384                 self.config.SetPath("/ModelMRU")
385                 self.modelFileHistory.Save(self.config)
386                 self.config.Flush()
387
388         def OnProfileMRU(self, e):
389                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
390                 path = self.profileFileHistory.GetHistoryFile(fileNum)
391                 # Update Profile MRU
392                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
393                 self.config.SetPath("/ProfileMRU")
394                 self.profileFileHistory.Save(self.config)
395                 self.config.Flush()
396                 # Load Profile
397                 profile.loadProfile(path)
398                 self.updateProfileToAllControls()
399
400         def addToProfileMRU(self, file):
401                 self.profileFileHistory.AddFileToHistory(file)
402                 self.config.SetPath("/ProfileMRU")
403                 self.profileFileHistory.Save(self.config)
404                 self.config.Flush()
405
406         def updateProfileToAllControls(self):
407                 self.scene.updateProfileToControls()
408                 self.normalSettingsPanel.updateProfileToControls()
409                 self.simpleSettingsPanel.updateProfileToControls()
410
411         def reloadSettingPanels(self):
412                 self.leftSizer.Detach(self.simpleSettingsPanel)
413                 self.leftSizer.Detach(self.normalSettingsPanel)
414                 self.simpleSettingsPanel.Destroy()
415                 self.normalSettingsPanel.Destroy()
416                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
417                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
418                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
419                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
420                 self.updateSliceMode()
421                 self.updateProfileToAllControls()
422
423         def updateMachineMenu(self):
424                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
425                 for item in self.machineMenu.GetMenuItems():
426                         self.machineMenu.RemoveItem(item)
427
428                 #Add a menu item for each machine configuration.
429                 for n in xrange(0, profile.getMachineCount()):
430                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
431                         if n == int(profile.getPreferenceFloat('active_machine')):
432                                 i.Check(True)
433                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
434
435                 self.machineMenu.AppendSeparator()
436
437                 i = self.machineMenu.Append(-1, _("Machine settings..."))
438                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
439
440                 #Add tools for machines.
441                 self.machineMenu.AppendSeparator()
442
443                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
444                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
445
446                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
447                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
448
449         def OnLoadProfile(self, e):
450                 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)
451                 dlg.SetWildcard("ini files (*.ini)|*.ini")
452                 if dlg.ShowModal() == wx.ID_OK:
453                         profileFile = dlg.GetPath()
454                         profile.loadProfile(profileFile)
455                         self.updateProfileToAllControls()
456
457                         # Update the Profile MRU
458                         self.addToProfileMRU(profileFile)
459                 dlg.Destroy()
460
461         def OnLoadProfileFromGcode(self, e):
462                 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)
463                 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
464                 if dlg.ShowModal() == wx.ID_OK:
465                         gcodeFile = dlg.GetPath()
466                         f = open(gcodeFile, 'r')
467                         hasProfile = False
468                         for line in f:
469                                 if line.startswith(';CURA_PROFILE_STRING:'):
470                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
471                                         if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
472                                                 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
473                                         hasProfile = True
474                         if hasProfile:
475                                 self.updateProfileToAllControls()
476                         else:
477                                 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)
478                 dlg.Destroy()
479
480         def OnSaveProfile(self, e):
481                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
482                 dlg.SetWildcard("ini files (*.ini)|*.ini")
483                 if dlg.ShowModal() == wx.ID_OK:
484                         profileFile = dlg.GetPath()
485                         if not profileFile.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
486                                 profileFile += '.ini'
487                         profile.saveProfile(profileFile)
488                 dlg.Destroy()
489
490         def OnResetProfile(self, e):
491                 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)
492                 result = dlg.ShowModal() == wx.ID_YES
493                 dlg.Destroy()
494                 if result:
495                         profile.resetProfile()
496                         self.updateProfileToAllControls()
497
498         def OnSimpleSwitch(self, e):
499                 profile.putPreference('startMode', 'Simple')
500                 self.updateSliceMode()
501
502         def OnNormalSwitch(self, e):
503                 profile.putPreference('startMode', 'Normal')
504                 self.updateSliceMode()
505
506         def OnDefaultMarlinFirmware(self, e):
507                 firmwareInstall.InstallFirmware()
508
509         def OnCustomFirmware(self, e):
510                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
511                         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)
512                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
513                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
514                 if dlg.ShowModal() == wx.ID_OK:
515                         filename = dlg.GetPath()
516                         dlg.Destroy()
517                         if not(os.path.exists(filename)):
518                                 return
519                         #For some reason my Ubuntu 10.10 crashes here.
520                         firmwareInstall.InstallFirmware(filename)
521
522         def OnFirstRunWizard(self, e):
523                 self.Hide()
524                 configWizard.configWizard()
525                 self.Show()
526                 self.reloadSettingPanels()
527
528         def OnSelectMachine(self, index):
529                 profile.setActiveMachine(index)
530                 self.reloadSettingPanels()
531
532         def OnBedLevelWizard(self, e):
533                 configWizard.bedLevelWizard()
534
535         def OnHeadOffsetWizard(self, e):
536                 configWizard.headOffsetWizard()
537
538         def OnExpertOpen(self, e):
539                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
540                 ecw.Centre()
541                 ecw.Show()
542
543         def OnMinecraftImport(self, e):
544                 mi = minecraftImport.minecraftImportWindow(self)
545                 mi.Centre()
546                 mi.Show(True)
547
548         def OnPIDDebugger(self, e):
549                 debugger = pidDebugger.debuggerWindow(self)
550                 debugger.Centre()
551                 debugger.Show(True)
552
553         def onCopyProfileClipboard(self, e):
554                 try:
555                         if not wx.TheClipboard.IsOpened():
556                                 wx.TheClipboard.Open()
557                                 clipData = wx.TextDataObject()
558                                 self.lastTriedClipboard = profile.getProfileString()
559                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
560                                 clipData.SetText(profileString)
561                                 wx.TheClipboard.SetData(clipData)
562                                 wx.TheClipboard.Close()
563                 except:
564                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
565
566         def OnCheckForUpdate(self, e):
567                 newVersion = version.checkForNewerVersion()
568                 if newVersion is not None:
569                         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:
570                                 webbrowser.open(newVersion)
571                 else:
572                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
573
574         def OnAbout(self, e):
575                 aboutBox = aboutWindow.aboutWindow()
576                 aboutBox.Centre()
577                 aboutBox.Show()
578
579         def OnClose(self, e):
580                 profile.saveProfile(profile.getDefaultProfilePath(), True)
581
582                 # Save the window position, size & state from the preferences file
583                 profile.putPreference('window_maximized', self.IsMaximized())
584                 if not self.IsMaximized() and not self.IsIconized():
585                         (posx, posy) = self.GetPosition()
586                         profile.putPreference('window_pos_x', posx)
587                         profile.putPreference('window_pos_y', posy)
588                         (width, height) = self.GetSize()
589                         profile.putPreference('window_width', width)
590                         profile.putPreference('window_height', height)
591
592                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
593                         isSimple = profile.getPreference('startMode') == 'Simple'
594                         if not isSimple:
595                                 self.normalSashPos = self.splitter.GetSashPosition()
596                         profile.putPreference('window_normal_sash', self.normalSashPos)
597
598                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
599                 print "Closing down"
600                 self.scene.OnPaint = lambda e : e
601                 self.scene._engine.cleanup()
602                 self.Destroy()
603
604         def OnQuit(self, e):
605                 self.Close()
606
607 class normalSettingsPanel(configBase.configPanelBase):
608         "Main user interface window"
609         def __init__(self, parent, callback = None):
610                 super(normalSettingsPanel, self).__init__(parent, callback)
611
612                 #Main tabs
613                 self.nb = wx.Notebook(self)
614                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
615                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
616
617                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
618                 self._addSettingsToPanels('basic', left, right)
619                 self.SizeLabelWidths(left, right)
620
621                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
622                 self._addSettingsToPanels('advanced', left, right)
623                 self.SizeLabelWidths(left, right)
624
625                 #Plugin page
626                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
627                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
628
629                 #Alteration page
630                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
631                         self.alterationPanel = None
632                 else:
633                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
634                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
635
636                 self.Bind(wx.EVT_SIZE, self.OnSize)
637
638                 self.nb.SetSize(self.GetSize())
639                 self.UpdateSize(self.printPanel)
640                 self.UpdateSize(self.advancedPanel)
641
642         def _addSettingsToPanels(self, category, left, right):
643                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
644
645                 p = left
646                 n = 0
647                 for title in profile.getSubCategoriesFor(category):
648                         n += 1 + len(profile.getSettingsForCategory(category, title))
649                         if n > count / 2:
650                                 p = right
651                         configBase.TitleRow(p, _(title))
652                         for s in profile.getSettingsForCategory(category, title):
653                                 configBase.SettingRow(p, s.getName())
654
655         def SizeLabelWidths(self, left, right):
656                 leftWidth = self.getLabelColumnWidth(left)
657                 rightWidth = self.getLabelColumnWidth(right)
658                 maxWidth = max(leftWidth, rightWidth)
659                 self.setLabelColumnWidth(left, maxWidth)
660                 self.setLabelColumnWidth(right, maxWidth)
661
662         def OnSize(self, e):
663                 # Make the size of the Notebook control the same size as this control
664                 self.nb.SetSize(self.GetSize())
665
666                 # Propegate the OnSize() event (just in case)
667                 e.Skip()
668
669                 # Perform out resize magic
670                 self.UpdateSize(self.printPanel)
671                 self.UpdateSize(self.advancedPanel)
672
673         def UpdateSize(self, configPanel):
674                 sizer = configPanel.GetSizer()
675
676                 # Pseudocde
677                 # if horizontal:
678                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
679                 #         switch to vertical
680                 # else:
681                 #     if width(col1) > (best_width(col1) + best_width(col1)):
682                 #         switch to horizontal
683                 #
684
685                 col1 = configPanel.leftPanel
686                 colSize1 = col1.GetSize()
687                 colBestSize1 = col1.GetBestSize()
688                 col2 = configPanel.rightPanel
689                 colSize2 = col2.GetSize()
690                 colBestSize2 = col2.GetBestSize()
691
692                 orientation = sizer.GetOrientation()
693
694                 if orientation == wx.HORIZONTAL:
695                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
696                                 configPanel.Freeze()
697                                 sizer = wx.BoxSizer(wx.VERTICAL)
698                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
699                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
700                                 configPanel.SetSizer(sizer)
701                                 #sizer.Layout()
702                                 configPanel.Layout()
703                                 self.Layout()
704                                 configPanel.Thaw()
705                 else:
706                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
707                                 configPanel.Freeze()
708                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
709                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
710                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
711                                 configPanel.SetSizer(sizer)
712                                 #sizer.Layout()
713                                 configPanel.Layout()
714                                 self.Layout()
715                                 configPanel.Thaw()
716
717         def updateProfileToControls(self):
718                 super(normalSettingsPanel, self).updateProfileToControls()
719                 if self.alterationPanel is not None:
720                         self.alterationPanel.updateProfileToControls()
721                 self.pluginPanel.updateProfileToControls()