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