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