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