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