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