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