chiark / gitweb /
cb293be226181314e71250c0e5bea02336f5fbcb
[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                 else:
388                         self.bedLevelWizardMenuItem.Enable(True)
389                         self.headOffsetWizardMenuItem.Enable(False)
390                         self.oneAtATime.Enable(True)
391                         if profile.getPreference('oneAtATime') == 'True':
392                                 self.oneAtATime.Check(True)
393                         else:
394                                 self.allAtOnceItem.Check(True)
395                 if int(profile.getMachineSetting('extruder_amount')) < 2:
396                         self.headOffsetWizardMenuItem.Enable(False)
397                 self.scene.updateProfileToControls()
398                 self.scene._scene.pushFree()
399
400         def onOneAtATimeSwitch(self, e):
401                 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
402                 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
403                         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)
404                 self.scene.updateProfileToControls()
405                 self.scene._scene.pushFree()
406                 self.scene.sceneUpdated()
407
408         def OnPreferences(self, e):
409                 prefDialog = preferencesDialog.preferencesDialog(self)
410                 prefDialog.Centre()
411                 prefDialog.Show()
412                 prefDialog.Raise()
413
414         def OnMachineSettings(self, e):
415                 prefDialog = preferencesDialog.machineSettingsDialog(self)
416                 prefDialog.Centre()
417                 prefDialog.Show()
418                 prefDialog.Raise()
419
420         def OnDropFiles(self, files):
421                 self.scene.loadFiles(files)
422
423         def OnModelMRU(self, e):
424                 fileNum = e.GetId() - self.ID_MRU_MODEL1
425                 path = self.modelFileHistory.GetHistoryFile(fileNum)
426                 # Update Model MRU
427                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
428                 self.config.SetPath("/ModelMRU")
429                 self.modelFileHistory.Save(self.config)
430                 self.config.Flush()
431                 # Load Model
432                 profile.putPreference('lastFile', path)
433                 filelist = [ path ]
434                 self.scene.loadFiles(filelist)
435
436         def addToModelMRU(self, file):
437                 self.modelFileHistory.AddFileToHistory(file)
438                 self.config.SetPath("/ModelMRU")
439                 self.modelFileHistory.Save(self.config)
440                 self.config.Flush()
441
442         def OnProfileMRU(self, e):
443                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
444                 path = self.profileFileHistory.GetHistoryFile(fileNum)
445                 # Update Profile MRU
446                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
447                 self.config.SetPath("/ProfileMRU")
448                 self.profileFileHistory.Save(self.config)
449                 self.config.Flush()
450                 # Load Profile
451                 profile.loadProfile(path)
452                 self.updateProfileToAllControls()
453
454         def addToProfileMRU(self, file):
455                 self.profileFileHistory.AddFileToHistory(file)
456                 self.config.SetPath("/ProfileMRU")
457                 self.profileFileHistory.Save(self.config)
458                 self.config.Flush()
459
460         def updateProfileToAllControls(self):
461                 self.scene.updateProfileToControls()
462                 self.normalSettingsPanel.updateProfileToControls()
463                 self.simpleSettingsPanel.updateProfileToControls()
464
465         def reloadSettingPanels(self, changedSliceMode = False):
466                 self.leftSizer.Detach(self.simpleSettingsPanel)
467                 self.leftSizer.Detach(self.normalSettingsPanel)
468                 self.simpleSettingsPanel.Destroy()
469                 self.normalSettingsPanel.Destroy()
470                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, self.scene.sceneUpdated)
471                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, self.scene.sceneUpdated)
472                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
473                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
474                 self.updateSliceMode(changedSliceMode)
475                 self.updateProfileToAllControls()
476
477         def updateMachineMenu(self):
478                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
479                 for item in self.machineMenu.GetMenuItems():
480                         self.machineMenu.RemoveItem(item)
481
482                 #Add a menu item for each machine configuration.
483                 for n in xrange(0, profile.getMachineCount()):
484                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
485                         if n == int(profile.getPreferenceFloat('active_machine')):
486                                 i.Check(True)
487                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
488
489                 self.machineMenu.AppendSeparator()
490                 i = self.machineMenu.Append(-1, _("Add new machine..."))
491                 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
492                 i = self.machineMenu.Append(-1, _("Machine settings..."))
493                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
494
495                 #Add tools for machines.
496                 self.machineMenu.AppendSeparator()
497
498                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
499                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
500
501                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
502                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
503
504         def OnLoadProfile(self, e):
505                 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)
506                 dlg.SetWildcard("ini files (*.ini)|*.ini")
507                 if dlg.ShowModal() == wx.ID_OK:
508                         profileFile = dlg.GetPath()
509                         profile.loadProfile(profileFile)
510                         self.updateProfileToAllControls()
511
512                         # Update the Profile MRU
513                         self.addToProfileMRU(profileFile)
514                 dlg.Destroy()
515
516         def OnLoadProfileFromGcode(self, e):
517                 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)
518                 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
519                 if dlg.ShowModal() == wx.ID_OK:
520                         gcodeFile = dlg.GetPath()
521                         f = open(gcodeFile, 'r')
522                         hasProfile = False
523                         for line in f:
524                                 if line.startswith(';CURA_PROFILE_STRING:'):
525                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
526                                         if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
527                                                 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
528                                         hasProfile = True
529                         if hasProfile:
530                                 self.updateProfileToAllControls()
531                         else:
532                                 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)
533                 dlg.Destroy()
534
535         def OnSaveProfile(self, e):
536                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
537                 dlg.SetWildcard("ini files (*.ini)|*.ini")
538                 if dlg.ShowModal() == wx.ID_OK:
539                         profile_filename = dlg.GetPath()
540                         if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
541                                 profile_filename += '.ini'
542                         profile.saveProfile(profile_filename)
543                 dlg.Destroy()
544
545         def OnSaveDifferences(self, e):
546                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
547                 dlg.SetWildcard("ini files (*.ini)|*.ini")
548                 if dlg.ShowModal() == wx.ID_OK:
549                         profile_filename = dlg.GetPath()
550                         if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
551                                 profile_filename += '.ini'
552                         profile.saveProfileDifferenceFromDefault(profile_filename)
553                 dlg.Destroy()
554
555         def OnResetProfile(self, e):
556                 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)
557                 result = dlg.ShowModal() == wx.ID_YES
558                 dlg.Destroy()
559                 if result:
560                         profile.resetProfile()
561                         self.updateProfileToAllControls()
562
563         def OnSimpleSwitch(self, e):
564                 profile.putPreference('startMode', 'Simple')
565                 self.updateSliceMode()
566
567         def OnNormalSwitch(self, e):
568                 profile.putPreference('startMode', 'Normal')
569                 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)
570                 result = dlg.ShowModal() == wx.ID_YES
571                 dlg.Destroy()
572                 if result:
573                         profile.resetProfile()
574                         for k, v in self.simpleSettingsPanel.getSettingOverrides().items():
575                                 if profile.isProfileSetting(k):
576                                         profile.putProfileSetting(k, v)
577                                 elif profile.isAlterationSetting(k):
578                                         profile.setAlterationFile(k, v)
579                         self.updateProfileToAllControls()
580                 self.updateSliceMode()
581
582         def OnDefaultMarlinFirmware(self, e):
583                 firmwareInstall.InstallFirmware(self)
584
585         def OnCustomFirmware(self, e):
586                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
587                         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)
588                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
589                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
590                 if dlg.ShowModal() == wx.ID_OK:
591                         filename = dlg.GetPath()
592                         dlg.Destroy()
593                         if not(os.path.exists(filename)):
594                                 return
595                         #For some reason my Ubuntu 10.10 crashes here.
596                         firmwareInstall.InstallFirmware(self, filename)
597
598         def OnAddNewMachine(self, e):
599                 self.Hide()
600                 wasSimple = profile.getPreference('startMode') == 'Simple'
601                 configWizard.ConfigWizard(True)
602                 isSimple = profile.getPreference('startMode') == 'Simple'
603                 self.Show()
604                 self.reloadSettingPanels(isSimple != wasSimple)
605                 self.updateMachineMenu()
606
607         def OnSelectMachine(self, index):
608                 profile.setActiveMachine(index)
609                 self.reloadSettingPanels(False)
610
611         def OnBedLevelWizard(self, e):
612                 configWizard.bedLevelWizard()
613
614         def OnHeadOffsetWizard(self, e):
615                 configWizard.headOffsetWizard()
616
617         def OnExpertOpen(self, e):
618                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
619                 ecw.Centre()
620                 ecw.Show()
621
622         def OnMinecraftImport(self, e):
623                 mi = minecraftImport.minecraftImportWindow(self)
624                 mi.Centre()
625                 mi.Show(True)
626
627         def OnPIDDebugger(self, e):
628                 debugger = pidDebugger.debuggerWindow(self)
629                 debugger.Centre()
630                 debugger.Show(True)
631
632         def OnAutoFirmwareUpdate(self, e):
633                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
634                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
635                 if dlg.ShowModal() == wx.ID_OK:
636                         filename = dlg.GetPath()
637                         dlg.Destroy()
638                         if not(os.path.exists(filename)):
639                                 return
640                         #For some reason my Ubuntu 10.10 crashes here.
641                         installer = firmwareInstall.AutoUpdateFirmware(self, filename)
642
643         def onCopyProfileClipboard(self, e):
644                 try:
645                         if not wx.TheClipboard.IsOpened():
646                                 wx.TheClipboard.Open()
647                                 clipData = wx.TextDataObject()
648                                 self.lastTriedClipboard = profile.getProfileString()
649                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
650                                 clipData.SetText(profileString)
651                                 wx.TheClipboard.SetData(clipData)
652                                 wx.TheClipboard.Close()
653                 except:
654                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
655
656         def OnCheckForUpdate(self, e):
657                 newVersion = version.checkForNewerVersion()
658                 if newVersion is not None:
659                         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:
660                                 webbrowser.open(newVersion)
661                 else:
662                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
663
664         def OnAbout(self, e):
665                 aboutBox = aboutWindow.aboutWindow(self)
666                 aboutBox.Centre()
667                 aboutBox.Show()
668                 aboutBox.Raise()
669
670         def OnClose(self, e):
671                 profile.saveProfile(profile.getDefaultProfilePath(), True)
672
673                 # Save the window position, size & state from the preferences file
674                 profile.putPreference('window_maximized', self.IsMaximized())
675                 if not self.IsMaximized() and not self.IsIconized():
676                         (posx, posy) = self.GetPosition()
677                         profile.putPreference('window_pos_x', posx)
678                         profile.putPreference('window_pos_y', posy)
679                         (width, height) = self.GetSize()
680                         profile.putPreference('window_width', width)
681                         profile.putPreference('window_height', height)
682
683                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
684                         isSimple = profile.getPreference('startMode') == 'Simple'
685                         if not isSimple:
686                                 self.normalSashPos = self.splitter.GetSashPosition()
687                         profile.putPreference('window_normal_sash', self.normalSashPos)
688
689                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
690                 print "Closing down"
691                 self.scene.OnPaint = lambda e : e
692                 self.scene.cleanup()
693                 self.Destroy()
694
695         def OnQuit(self, e):
696                 self.Close()
697
698 class normalSettingsPanel(configBase.configPanelBase):
699         "Main user interface window"
700         def __init__(self, parent, callback = None):
701                 super(normalSettingsPanel, self).__init__(parent, callback)
702
703                 #Main tabs
704                 self.nb = wx.Notebook(self)
705                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
706                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
707
708                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, _('Basic'))
709                 self._addSettingsToPanels('basic', left, right)
710                 self.SizeLabelWidths(left, right)
711
712                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, _('Advanced'))
713                 self._addSettingsToPanels('advanced', left, right)
714                 self.SizeLabelWidths(left, right)
715
716                 #Plugin page
717                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
718                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
719
720                 #Alteration page
721                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
722                         self.alterationPanel = None
723                 else:
724                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
725                         self.nb.AddPage(self.alterationPanel, _("Start/End-GCode"))
726
727                 self.Bind(wx.EVT_SIZE, self.OnSize)
728
729                 self.nb.SetSize(self.GetSize())
730                 self.UpdateSize(self.printPanel)
731                 self.UpdateSize(self.advancedPanel)
732
733         def _addSettingsToPanels(self, category, left, right):
734                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
735
736                 p = left
737                 n = 0
738                 for title in profile.getSubCategoriesFor(category):
739                         n += 1 + len(profile.getSettingsForCategory(category, title))
740                         if n > count / 2:
741                                 p = right
742                         configBase.TitleRow(p, _(title))
743                         for s in profile.getSettingsForCategory(category, title):
744                                 configBase.SettingRow(p, s.getName())
745
746         def SizeLabelWidths(self, left, right):
747                 leftWidth = self.getLabelColumnWidth(left)
748                 rightWidth = self.getLabelColumnWidth(right)
749                 maxWidth = max(leftWidth, rightWidth)
750                 self.setLabelColumnWidth(left, maxWidth)
751                 self.setLabelColumnWidth(right, maxWidth)
752
753         def OnSize(self, e):
754                 # Make the size of the Notebook control the same size as this control
755                 self.nb.SetSize(self.GetSize())
756
757                 # Propegate the OnSize() event (just in case)
758                 e.Skip()
759
760                 # Perform out resize magic
761                 self.UpdateSize(self.printPanel)
762                 self.UpdateSize(self.advancedPanel)
763
764         def UpdateSize(self, configPanel):
765                 sizer = configPanel.GetSizer()
766
767                 # Pseudocde
768                 # if horizontal:
769                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
770                 #         switch to vertical
771                 # else:
772                 #     if width(col1) > (best_width(col1) + best_width(col1)):
773                 #         switch to horizontal
774                 #
775
776                 col1 = configPanel.leftPanel
777                 colSize1 = col1.GetSize()
778                 colBestSize1 = col1.GetBestSize()
779                 col2 = configPanel.rightPanel
780                 colSize2 = col2.GetSize()
781                 colBestSize2 = col2.GetBestSize()
782
783                 orientation = sizer.GetOrientation()
784
785                 if orientation == wx.HORIZONTAL:
786                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
787                                 configPanel.Freeze()
788                                 sizer = wx.BoxSizer(wx.VERTICAL)
789                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
790                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
791                                 configPanel.SetSizer(sizer)
792                                 #sizer.Layout()
793                                 configPanel.Layout()
794                                 self.Layout()
795                                 configPanel.Thaw()
796                 else:
797                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
798                                 configPanel.Freeze()
799                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
800                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
801                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
802                                 configPanel.SetSizer(sizer)
803                                 #sizer.Layout()
804                                 configPanel.Layout()
805                                 self.Layout()
806                                 configPanel.Thaw()
807
808         def updateProfileToControls(self):
809                 super(normalSettingsPanel, self).updateProfileToControls()
810                 if self.alterationPanel is not None:
811                         self.alterationPanel.updateProfileToControls()
812                 self.pluginPanel.updateProfileToControls()