chiark / gitweb /
Fix the profile_string handling in loading the profile from 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                                         if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
469                                                 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
470                                         hasProfile = True
471                         if hasProfile:
472                                 self.updateProfileToAllControls()
473                         else:
474                                 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)
475                 dlg.Destroy()
476
477         def OnSaveProfile(self, e):
478                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
479                 dlg.SetWildcard("ini files (*.ini)|*.ini")
480                 if dlg.ShowModal() == wx.ID_OK:
481                         profileFile = dlg.GetPath()
482                         if platform.system() == 'Linux': #hack for linux, as for some reason the .ini is not appended.
483                                 profileFile += '.ini'
484                         profile.saveProfile(profileFile)
485                 dlg.Destroy()
486
487         def OnResetProfile(self, e):
488                 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)
489                 result = dlg.ShowModal() == wx.ID_YES
490                 dlg.Destroy()
491                 if result:
492                         profile.resetProfile()
493                         self.updateProfileToAllControls()
494
495         def OnSimpleSwitch(self, e):
496                 profile.putPreference('startMode', 'Simple')
497                 self.updateSliceMode()
498
499         def OnNormalSwitch(self, e):
500                 profile.putPreference('startMode', 'Normal')
501                 self.updateSliceMode()
502
503         def OnDefaultMarlinFirmware(self, e):
504                 firmwareInstall.InstallFirmware()
505
506         def OnCustomFirmware(self, e):
507                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
508                         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)
509                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
510                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
511                 if dlg.ShowModal() == wx.ID_OK:
512                         filename = dlg.GetPath()
513                         if not(os.path.exists(filename)):
514                                 return
515                         #For some reason my Ubuntu 10.10 crashes here.
516                         firmwareInstall.InstallFirmware(filename)
517
518         def OnFirstRunWizard(self, e):
519                 self.Hide()
520                 configWizard.configWizard()
521                 self.Show()
522                 self.reloadSettingPanels()
523
524         def OnSelectMachine(self, index):
525                 profile.setActiveMachine(index)
526                 self.reloadSettingPanels()
527
528         def OnBedLevelWizard(self, e):
529                 configWizard.bedLevelWizard()
530
531         def OnHeadOffsetWizard(self, e):
532                 configWizard.headOffsetWizard()
533
534         def OnExpertOpen(self, e):
535                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
536                 ecw.Centre()
537                 ecw.Show()
538
539         def OnMinecraftImport(self, e):
540                 mi = minecraftImport.minecraftImportWindow(self)
541                 mi.Centre()
542                 mi.Show(True)
543
544         def OnPIDDebugger(self, e):
545                 debugger = pidDebugger.debuggerWindow(self)
546                 debugger.Centre()
547                 debugger.Show(True)
548
549         def onCopyProfileClipboard(self, e):
550                 try:
551                         if not wx.TheClipboard.IsOpened():
552                                 wx.TheClipboard.Open()
553                                 clipData = wx.TextDataObject()
554                                 self.lastTriedClipboard = profile.getProfileString()
555                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
556                                 clipData.SetText(profileString)
557                                 wx.TheClipboard.SetData(clipData)
558                                 wx.TheClipboard.Close()
559                 except:
560                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
561
562         def OnCheckForUpdate(self, e):
563                 newVersion = version.checkForNewerVersion()
564                 if newVersion is not None:
565                         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:
566                                 webbrowser.open(newVersion)
567                 else:
568                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
569
570         def OnAbout(self, e):
571                 aboutBox = aboutWindow.aboutWindow()
572                 aboutBox.Centre()
573                 aboutBox.Show()
574
575         def OnClose(self, e):
576                 profile.saveProfile(profile.getDefaultProfilePath(), True)
577
578                 # Save the window position, size & state from the preferences file
579                 profile.putPreference('window_maximized', self.IsMaximized())
580                 if not self.IsMaximized() and not self.IsIconized():
581                         (posx, posy) = self.GetPosition()
582                         profile.putPreference('window_pos_x', posx)
583                         profile.putPreference('window_pos_y', posy)
584                         (width, height) = self.GetSize()
585                         profile.putPreference('window_width', width)
586                         profile.putPreference('window_height', height)
587
588                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
589                         isSimple = profile.getPreference('startMode') == 'Simple'
590                         if not isSimple:
591                                 self.normalSashPos = self.splitter.GetSashPosition()
592                         profile.putPreference('window_normal_sash', self.normalSashPos)
593
594                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
595                 print "Closing down"
596                 self.scene.OnPaint = lambda e : e
597                 self.scene._engine.cleanup()
598                 self.Destroy()
599
600         def OnQuit(self, e):
601                 self.Close()
602
603 class normalSettingsPanel(configBase.configPanelBase):
604         "Main user interface window"
605         def __init__(self, parent, callback = None):
606                 super(normalSettingsPanel, self).__init__(parent, callback)
607
608                 #Main tabs
609                 self.nb = wx.Notebook(self)
610                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
611                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
612
613                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
614                 self._addSettingsToPanels('basic', left, right)
615                 self.SizeLabelWidths(left, right)
616
617                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
618                 self._addSettingsToPanels('advanced', left, right)
619                 self.SizeLabelWidths(left, right)
620
621                 #Plugin page
622                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
623                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
624
625                 #Alteration page
626                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
627                         self.alterationPanel = None
628                 else:
629                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
630                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
631
632                 self.Bind(wx.EVT_SIZE, self.OnSize)
633
634                 self.nb.SetSize(self.GetSize())
635                 self.UpdateSize(self.printPanel)
636                 self.UpdateSize(self.advancedPanel)
637
638         def _addSettingsToPanels(self, category, left, right):
639                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
640
641                 p = left
642                 n = 0
643                 for title in profile.getSubCategoriesFor(category):
644                         n += 1 + len(profile.getSettingsForCategory(category, title))
645                         if n > count / 2:
646                                 p = right
647                         configBase.TitleRow(p, _(title))
648                         for s in profile.getSettingsForCategory(category, title):
649                                 configBase.SettingRow(p, s.getName())
650
651         def SizeLabelWidths(self, left, right):
652                 leftWidth = self.getLabelColumnWidth(left)
653                 rightWidth = self.getLabelColumnWidth(right)
654                 maxWidth = max(leftWidth, rightWidth)
655                 self.setLabelColumnWidth(left, maxWidth)
656                 self.setLabelColumnWidth(right, maxWidth)
657
658         def OnSize(self, e):
659                 # Make the size of the Notebook control the same size as this control
660                 self.nb.SetSize(self.GetSize())
661
662                 # Propegate the OnSize() event (just in case)
663                 e.Skip()
664
665                 # Perform out resize magic
666                 self.UpdateSize(self.printPanel)
667                 self.UpdateSize(self.advancedPanel)
668
669         def UpdateSize(self, configPanel):
670                 sizer = configPanel.GetSizer()
671
672                 # Pseudocde
673                 # if horizontal:
674                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
675                 #         switch to vertical
676                 # else:
677                 #     if width(col1) > (best_width(col1) + best_width(col1)):
678                 #         switch to horizontal
679                 #
680
681                 col1 = configPanel.leftPanel
682                 colSize1 = col1.GetSize()
683                 colBestSize1 = col1.GetBestSize()
684                 col2 = configPanel.rightPanel
685                 colSize2 = col2.GetSize()
686                 colBestSize2 = col2.GetBestSize()
687
688                 orientation = sizer.GetOrientation()
689
690                 if orientation == wx.HORIZONTAL:
691                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
692                                 configPanel.Freeze()
693                                 sizer = wx.BoxSizer(wx.VERTICAL)
694                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
695                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
696                                 configPanel.SetSizer(sizer)
697                                 #sizer.Layout()
698                                 configPanel.Layout()
699                                 self.Layout()
700                                 configPanel.Thaw()
701                 else:
702                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
703                                 configPanel.Freeze()
704                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
705                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
706                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
707                                 configPanel.SetSizer(sizer)
708                                 #sizer.Layout()
709                                 configPanel.Layout()
710                                 self.Layout()
711                                 configPanel.Thaw()
712
713         def updateProfileToControls(self):
714                 super(normalSettingsPanel, self).updateProfileToControls()
715                 if self.alterationPanel is not None:
716                         self.alterationPanel.updateProfileToControls()
717                 self.pluginPanel.updateProfileToControls()