chiark / gitweb /
Merge branch 'SteamEngine' of github.com:daid/Cura into SteamEngine
[cura.git] / Cura / gui / mainWindow.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import wx
5 import os
6 import webbrowser
7
8 from Cura.gui import configBase
9 from Cura.gui import expertConfig
10 from Cura.gui import alterationPanel
11 from Cura.gui import pluginPanel
12 from Cura.gui import preferencesDialog
13 from Cura.gui import configWizard
14 from Cura.gui import firmwareInstall
15 from Cura.gui import simpleMode
16 from Cura.gui import sceneView
17 from Cura.gui.util import dropTarget
18 #from Cura.gui.tools import batchRun
19 from Cura.gui.tools import pidDebugger
20 from Cura.gui.tools import minecraftImport
21 from Cura.util import profile
22 from Cura.util import version
23 from Cura.util import meshLoader
24
25 class mainWindow(wx.Frame):
26         def __init__(self):
27                 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
28
29                 self.extruderCount = int(profile.getMachineSetting('extruder_amount'))
30
31                 wx.EVT_CLOSE(self, self.OnClose)
32
33                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions() + ['.g', '.gcode', '.ini']))
34
35                 self.normalModeOnlyItems = []
36
37                 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
38                 self.config = wx.FileConfig(appName="Cura",
39                                                 localFilename=mruFile,
40                                                 style=wx.CONFIG_USE_LOCAL_FILE)
41
42                 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)]
43                 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
44                 self.config.SetPath("/ModelMRU")
45                 self.modelFileHistory.Load(self.config)
46
47                 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)]
48                 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
49                 self.config.SetPath("/ProfileMRU")
50                 self.profileFileHistory.Load(self.config)
51
52                 self.menubar = wx.MenuBar()
53                 self.fileMenu = wx.Menu()
54                 i = self.fileMenu.Append(-1, _("Load model file...\tCTRL+L"))
55                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i)
56                 i = self.fileMenu.Append(-1, _("Save model...\tCTRL+S"))
57                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
58                 i = self.fileMenu.Append(-1, _("Clear platform"))
59                 self.Bind(wx.EVT_MENU, lambda e: self.scene.OnDeleteAll(e), i)
60
61                 self.fileMenu.AppendSeparator()
62                 i = self.fileMenu.Append(-1, _("Print...\tCTRL+P"))
63                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showPrintWindow(), i)
64                 i = self.fileMenu.Append(-1, _("Save GCode..."))
65                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
66                 i = self.fileMenu.Append(-1, _("Show slice engine log..."))
67                 self.Bind(wx.EVT_MENU, lambda e: self.scene._showSliceLog(), i)
68
69                 self.fileMenu.AppendSeparator()
70                 i = self.fileMenu.Append(-1, _("Open Profile..."))
71                 self.normalModeOnlyItems.append(i)
72                 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
73                 i = self.fileMenu.Append(-1, _("Save Profile..."))
74                 self.normalModeOnlyItems.append(i)
75                 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
76                 i = self.fileMenu.Append(-1, _("Load Profile from GCode..."))
77                 self.normalModeOnlyItems.append(i)
78                 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
79                 self.fileMenu.AppendSeparator()
80                 i = self.fileMenu.Append(-1, _("Reset Profile to default"))
81                 self.normalModeOnlyItems.append(i)
82                 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
83
84                 self.fileMenu.AppendSeparator()
85                 i = self.fileMenu.Append(-1, _("Preferences...\tCTRL+,"))
86                 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
87                 i = self.fileMenu.Append(-1, _("Machine settings..."))
88                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
89                 self.fileMenu.AppendSeparator()
90
91                 # Model MRU list
92                 modelHistoryMenu = wx.Menu()
93                 self.fileMenu.AppendMenu(wx.NewId(), _("&Recent Model Files"), modelHistoryMenu)
94                 self.modelFileHistory.UseMenu(modelHistoryMenu)
95                 self.modelFileHistory.AddFilesToMenu()
96                 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
97
98                 # Profle MRU list
99                 profileHistoryMenu = wx.Menu()
100                 self.fileMenu.AppendMenu(wx.NewId(), _("&Recent Profile Files"), profileHistoryMenu)
101                 self.profileFileHistory.UseMenu(profileHistoryMenu)
102                 self.profileFileHistory.AddFilesToMenu()
103                 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
104
105                 self.fileMenu.AppendSeparator()
106                 i = self.fileMenu.Append(wx.ID_EXIT, _("Quit"))
107                 self.Bind(wx.EVT_MENU, self.OnQuit, i)
108                 self.menubar.Append(self.fileMenu, _("&File"))
109
110                 toolsMenu = wx.Menu()
111
112                 i = toolsMenu.Append(-1, _("Switch to quickprint..."))
113                 self.switchToQuickprintMenuItem = i
114                 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
115
116                 i = toolsMenu.Append(-1, _("Switch to full settings..."))
117                 self.switchToNormalMenuItem = i
118                 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
119
120                 #toolsMenu.AppendSeparator()
121                 #i = toolsMenu.Append(-1, 'Batch run...')
122                 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
123                 #self.normalModeOnlyItems.append(i)
124
125                 if minecraftImport.hasMinecraft():
126                         i = toolsMenu.Append(-1, _("Minecraft import..."))
127                         self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
128
129                 if version.isDevVersion():
130                         i = toolsMenu.Append(-1, _("PID Debugger..."))
131                         self.Bind(wx.EVT_MENU, self.OnPIDDebugger, i)
132
133                 i = toolsMenu.Append(-1, _("Copy profile to clipboard"))
134                 self.Bind(wx.EVT_MENU, self.onCopyProfileClipboard,i)
135                 self.menubar.Append(toolsMenu, _("Tools"))
136
137                 #Machine menu for machine configuration/tooling
138                 self.machineMenu = wx.Menu()
139                 self.updateMachineMenu()
140
141                 self.menubar.Append(self.machineMenu, _("Machine"))
142
143                 expertMenu = wx.Menu()
144                 i = expertMenu.Append(-1, _("Open expert settings..."))
145                 self.normalModeOnlyItems.append(i)
146                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
147                 expertMenu.AppendSeparator()
148                 i = expertMenu.Append(-1, _("Run first run wizard..."))
149                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
150                 i = expertMenu.Append(-1, _("Run bed leveling wizard..."))
151                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
152                 if self.extruderCount > 1:
153                         i = expertMenu.Append(-1, _("Run head offset wizard..."))
154                         self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, i)
155
156                 self.menubar.Append(expertMenu, _("Expert"))
157
158                 helpMenu = wx.Menu()
159                 i = helpMenu.Append(-1, _("Online documentation..."))
160                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
161                 i = helpMenu.Append(-1, _("Report a problem..."))
162                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
163                 i = helpMenu.Append(-1, _("Check for update..."))
164                 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
165                 i = helpMenu.Append(-1, _("Open YouMagine website..."))
166                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://www.youmagine.com/'), i)
167                 i = helpMenu.Append(-1, _("About Cura..."))
168                 self.Bind(wx.EVT_MENU, self.OnAbout, i)
169                 self.menubar.Append(helpMenu, _("Help"))
170                 self.SetMenuBar(self.menubar)
171
172                 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
173                 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
174                 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
175                 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
176
177                 ##Gui components##
178                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
179                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
180
181                 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
182                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
183                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
184                 self.leftPane.SetSizer(self.leftSizer)
185
186                 #Preview window
187                 self.scene = sceneView.SceneView(self.rightPane)
188
189                 #Main sizer, to position the preview window, buttons and tab control
190                 sizer = wx.BoxSizer()
191                 self.rightPane.SetSizer(sizer)
192                 sizer.Add(self.scene, 1, flag=wx.EXPAND)
193
194                 # Main window sizer
195                 sizer = wx.BoxSizer(wx.VERTICAL)
196                 self.SetSizer(sizer)
197                 sizer.Add(self.splitter, 1, wx.EXPAND)
198                 sizer.Layout()
199                 self.sizer = sizer
200
201                 self.updateProfileToAllControls()
202
203                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
204
205                 self.simpleSettingsPanel.Show(False)
206                 self.normalSettingsPanel.Show(False)
207
208                 # Set default window size & position
209                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
210                 self.Centre()
211
212                 #Timer set; used to check if profile is on the clipboard
213                 self.timer = wx.Timer(self)
214                 self.Bind(wx.EVT_TIMER, self.onTimer)
215                 self.timer.Start(1000)
216                 self.lastTriedClipboard = profile.getProfileString()
217
218                 # Restore the window position, size & state from the preferences file
219                 try:
220                         if profile.getPreference('window_maximized') == 'True':
221                                 self.Maximize(True)
222                         else:
223                                 posx = int(profile.getPreference('window_pos_x'))
224                                 posy = int(profile.getPreference('window_pos_y'))
225                                 width = int(profile.getPreference('window_width'))
226                                 height = int(profile.getPreference('window_height'))
227                                 if posx > 0 or posy > 0:
228                                         self.SetPosition((posx,posy))
229                                 if width > 0 and height > 0:
230                                         self.SetSize((width,height))
231
232                         self.normalSashPos = int(profile.getPreference('window_normal_sash'))
233                 except:
234                         self.normalSashPos = 0
235                         self.Maximize(True)
236                 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
237                         self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
238
239                 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
240
241                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
242                         self.Centre()
243                 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
244                         self.Centre()
245                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
246                         self.SetSize((800,600))
247                         self.Centre()
248
249                 self.updateSliceMode()
250
251         def onTimer(self, e):
252                 #Check if there is something in the clipboard
253                 profileString = ""
254                 try:
255                         if not wx.TheClipboard.IsOpened():
256                                 wx.TheClipboard.Open()
257                                 do = wx.TextDataObject()
258                                 if wx.TheClipboard.GetData(do):
259                                         profileString = do.GetText()
260                                 wx.TheClipboard.Close()
261
262                                 if "CURA_PROFILE_STRING:" in profileString:
263                                         #print "Found correct syntax on clipboard"
264                                         profileString = profileString.replace("\n","")
265                                         profileString = profileString.replace("CURA_PROFILE_STRING:", "")
266                                         if profileString != self.lastTriedClipboard:
267                                                 self.lastTriedClipboard = profileString
268                                                 profile.setProfileFromString(profileString)
269                                                 print "changed profile"
270                                                 self.updateProfileToAllControls()
271                 except:
272                         print "Unable to read from clipboard"
273
274
275         def updateSliceMode(self):
276                 isSimple = profile.getPreference('startMode') == 'Simple'
277
278                 self.normalSettingsPanel.Show(not isSimple)
279                 self.simpleSettingsPanel.Show(isSimple)
280                 self.leftPane.Layout()
281
282                 for i in self.normalModeOnlyItems:
283                         i.Enable(not isSimple)
284                 self.switchToQuickprintMenuItem.Enable(not isSimple)
285                 self.switchToNormalMenuItem.Enable(isSimple)
286
287                 # Set splitter sash position & size
288                 if isSimple:
289                         # Save normal mode sash
290                         self.normalSashPos = self.splitter.GetSashPosition()
291
292                         # Change location of sash to width of quick mode pane
293                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
294                         self.splitter.SetSashPosition(width, True)
295
296                         # Disable sash
297                         self.splitter.SetSashSize(0)
298                 else:
299                         self.splitter.SetSashPosition(self.normalSashPos, True)
300                         # Enabled sash
301                         self.splitter.SetSashSize(4)
302                 self.scene.updateProfileToControls()
303
304         def OnPreferences(self, e):
305                 prefDialog = preferencesDialog.preferencesDialog(self)
306                 prefDialog.Centre()
307                 prefDialog.Show()
308
309         def OnMachineSettings(self, e):
310                 prefDialog = preferencesDialog.machineSettingsDialog(self)
311                 prefDialog.Centre()
312                 prefDialog.Show()
313
314         def OnDropFiles(self, files):
315                 if len(files) > 0:
316                         profile.setPluginConfig([])
317                         self.updateProfileToAllControls()
318                 self.scene.loadFiles(files)
319
320         def OnModelMRU(self, e):
321                 fileNum = e.GetId() - self.ID_MRU_MODEL1
322                 path = self.modelFileHistory.GetHistoryFile(fileNum)
323                 # Update Model MRU
324                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
325                 self.config.SetPath("/ModelMRU")
326                 self.modelFileHistory.Save(self.config)
327                 self.config.Flush()
328                 # Load Model
329                 profile.putPreference('lastFile', path)
330                 filelist = [ path ]
331                 self.scene.loadFiles(filelist)
332
333         def addToModelMRU(self, file):
334                 self.modelFileHistory.AddFileToHistory(file)
335                 self.config.SetPath("/ModelMRU")
336                 self.modelFileHistory.Save(self.config)
337                 self.config.Flush()
338
339         def OnProfileMRU(self, e):
340                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
341                 path = self.profileFileHistory.GetHistoryFile(fileNum)
342                 # Update Profile MRU
343                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
344                 self.config.SetPath("/ProfileMRU")
345                 self.profileFileHistory.Save(self.config)
346                 self.config.Flush()
347                 # Load Profile
348                 profile.loadProfile(path)
349                 self.updateProfileToAllControls()
350
351         def addToProfileMRU(self, file):
352                 self.profileFileHistory.AddFileToHistory(file)
353                 self.config.SetPath("/ProfileMRU")
354                 self.profileFileHistory.Save(self.config)
355                 self.config.Flush()
356
357         def updateProfileToAllControls(self):
358                 self.scene.updateProfileToControls()
359                 self.normalSettingsPanel.updateProfileToControls()
360                 self.simpleSettingsPanel.updateProfileToControls()
361
362         def reloadSettingPanels(self):
363                 self.leftSizer.Detach(self.simpleSettingsPanel)
364                 self.leftSizer.Detach(self.normalSettingsPanel)
365                 self.simpleSettingsPanel.Destroy()
366                 self.normalSettingsPanel.Destroy()
367                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
368                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
369                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
370                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
371                 self.updateSliceMode()
372                 self.updateProfileToAllControls()
373
374         def updateMachineMenu(self):
375                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
376                 for item in self.machineMenu.GetMenuItems():
377                         self.machineMenu.RemoveItem(item)
378
379                 #Add a menu item for each machine configuration.
380                 for n in xrange(0, profile.getMachineCount()):
381                         i = self.machineMenu.Append(n, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
382                         if n == int(profile.getPreferenceFloat('active_machine')):
383                                 i.Check(True)
384                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId()), i)
385
386                 self.machineMenu.AppendSeparator()
387                 i = self.machineMenu.Append(-1, _("Add new machine..."))
388                 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
389
390                 #Add tools for machines.
391                 self.machineMenu.AppendSeparator()
392                 i = self.machineMenu.Append(-1, _("Install custom firmware"))
393                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
394                 i = self.machineMenu.Append(-1, _("Install default Marlin firmware"))
395                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
396
397         def OnLoadProfile(self, e):
398                 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)
399                 dlg.SetWildcard("ini files (*.ini)|*.ini")
400                 if dlg.ShowModal() == wx.ID_OK:
401                         profileFile = dlg.GetPath()
402                         profile.loadProfile(profileFile)
403                         self.updateProfileToAllControls()
404
405                         # Update the Profile MRU
406                         self.addToProfileMRU(profileFile)
407                 dlg.Destroy()
408
409         def OnLoadProfileFromGcode(self, e):
410                 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)
411                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
412                 if dlg.ShowModal() == wx.ID_OK:
413                         gcodeFile = dlg.GetPath()
414                         f = open(gcodeFile, 'r')
415                         hasProfile = False
416                         for line in f:
417                                 if line.startswith(';CURA_PROFILE_STRING:'):
418                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
419                                         hasProfile = True
420                         if hasProfile:
421                                 self.updateProfileToAllControls()
422                         else:
423                                 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)
424                 dlg.Destroy()
425
426         def OnSaveProfile(self, e):
427                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
428                 dlg.SetWildcard("ini files (*.ini)|*.ini")
429                 if dlg.ShowModal() == wx.ID_OK:
430                         profileFile = dlg.GetPath()
431                         profile.saveProfile(profileFile)
432                 dlg.Destroy()
433
434         def OnResetProfile(self, e):
435                 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)
436                 result = dlg.ShowModal() == wx.ID_YES
437                 dlg.Destroy()
438                 if result:
439                         profile.resetProfile()
440                         self.updateProfileToAllControls()
441
442         def OnSimpleSwitch(self, e):
443                 profile.putPreference('startMode', 'Simple')
444                 self.updateSliceMode()
445
446         def OnNormalSwitch(self, e):
447                 profile.putPreference('startMode', 'Normal')
448                 self.updateSliceMode()
449
450         def OnDefaultMarlinFirmware(self, e):
451                 firmwareInstall.InstallFirmware()
452
453         def OnCustomFirmware(self, e):
454                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
455                         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)
456                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
457                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
458                 if dlg.ShowModal() == wx.ID_OK:
459                         filename = dlg.GetPath()
460                         if not(os.path.exists(filename)):
461                                 return
462                         #For some reason my Ubuntu 10.10 crashes here.
463                         firmwareInstall.InstallFirmware(filename)
464
465         def OnFirstRunWizard(self, e):
466                 self.Hide()
467                 configWizard.configWizard()
468                 self.Show()
469                 self.reloadSettingPanels()
470
471         def OnAddNewMachine(self, e):
472                 self.Hide()
473                 profile.setActiveMachine(profile.getMachineCount())
474                 configWizard.configWizard(True)
475                 self.Show()
476                 self.reloadSettingPanels()
477                 self.updateMachineMenu()
478
479         def OnSelectMachine(self, index):
480                 profile.setActiveMachine(index)
481                 self.reloadSettingPanels()
482
483         def OnBedLevelWizard(self, e):
484                 configWizard.bedLevelWizard()
485
486         def OnHeadOffsetWizard(self, e):
487                 configWizard.headOffsetWizard()
488
489         def OnExpertOpen(self, e):
490                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
491                 ecw.Centre()
492                 ecw.Show()
493
494         def OnMinecraftImport(self, e):
495                 mi = minecraftImport.minecraftImportWindow(self)
496                 mi.Centre()
497                 mi.Show(True)
498
499         def OnPIDDebugger(self, e):
500                 debugger = pidDebugger.debuggerWindow(self)
501                 debugger.Centre()
502                 debugger.Show(True)
503
504         def onCopyProfileClipboard(self, e):
505                 try:
506                         if not wx.TheClipboard.IsOpened():
507                                 wx.TheClipboard.Open()
508                                 clipData = wx.TextDataObject()
509                                 self.lastTriedClipboard = profile.getProfileString()
510                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
511                                 clipData.SetText(profileString)
512                                 wx.TheClipboard.SetData(clipData)
513                                 wx.TheClipboard.Close()
514                 except:
515                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
516
517         def OnCheckForUpdate(self, e):
518                 newVersion = version.checkForNewerVersion()
519                 if newVersion is not None:
520                         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:
521                                 webbrowser.open(newVersion)
522                 else:
523                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
524
525         def OnAbout(self, e):
526                 info = wx.AboutDialogInfo()
527                 info.SetName("Cura")
528                 info.SetDescription(_("End solution for Open Source Fused Filament Fabrication 3D printing."))
529                 info.SetWebSite('http://software.ultimaker.com/')
530                 info.SetCopyright(_("Copyright (C) David Braam"))
531                 info.SetLicence("""
532     This program is free software: you can redistribute it and/or modify
533     it under the terms of the GNU Affero General Public License as published by
534     the Free Software Foundation, either version 3 of the License, or
535     (at your option) any later version.
536
537     This program is distributed in the hope that it will be useful,
538     but WITHOUT ANY WARRANTY; without even the implied warranty of
539     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
540     GNU Affero General Public License for more details.
541
542     You should have received a copy of the GNU Affero General Public License
543     along with this program.  If not, see <http://www.gnu.org/licenses/>.
544 """)
545                 wx.AboutBox(info)
546
547         def OnClose(self, e):
548                 profile.saveProfile(profile.getDefaultProfilePath())
549
550                 # Save the window position, size & state from the preferences file
551                 profile.putPreference('window_maximized', self.IsMaximized())
552                 if not self.IsMaximized() and not self.IsIconized():
553                         (posx, posy) = self.GetPosition()
554                         profile.putPreference('window_pos_x', posx)
555                         profile.putPreference('window_pos_y', posy)
556                         (width, height) = self.GetSize()
557                         profile.putPreference('window_width', width)
558                         profile.putPreference('window_height', height)
559
560                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
561                         isSimple = profile.getPreference('startMode') == 'Simple'
562                         if not isSimple:
563                                 self.normalSashPos = self.splitter.GetSashPosition()
564                         profile.putPreference('window_normal_sash', self.normalSashPos)
565
566                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
567                 print "Closing down"
568                 self.scene.OnPaint = lambda e : e
569                 self.scene._slicer.cleanup()
570                 self.Destroy()
571
572         def OnQuit(self, e):
573                 self.Close()
574
575 class normalSettingsPanel(configBase.configPanelBase):
576         "Main user interface window"
577         def __init__(self, parent, callback = None):
578                 super(normalSettingsPanel, self).__init__(parent, callback)
579
580                 #Main tabs
581                 self.nb = wx.Notebook(self)
582                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
583                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
584
585                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
586                 self._addSettingsToPanels('basic', left, right)
587                 self.SizeLabelWidths(left, right)
588
589                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
590                 self._addSettingsToPanels('advanced', left, right)
591                 self.SizeLabelWidths(left, right)
592
593                 #Plugin page
594                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
595                 if len(self.pluginPanel.pluginList) > 0:
596                         self.nb.AddPage(self.pluginPanel, _("Plugins"))
597                 else:
598                         self.pluginPanel.Show(False)
599
600                 #Alteration page
601                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
602                         self.alterationPanel = None
603                 else:
604                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
605                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
606
607                 self.Bind(wx.EVT_SIZE, self.OnSize)
608
609                 self.nb.SetSize(self.GetSize())
610                 self.UpdateSize(self.printPanel)
611                 self.UpdateSize(self.advancedPanel)
612
613         def _addSettingsToPanels(self, category, left, right):
614                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
615
616                 p = left
617                 n = 0
618                 for title in profile.getSubCategoriesFor(category):
619                         n += 1 + len(profile.getSettingsForCategory(category, title))
620                         if n > count / 2:
621                                 p = right
622                         configBase.TitleRow(p, title)
623                         for s in profile.getSettingsForCategory(category, title):
624                                 configBase.SettingRow(p, s.getName())
625
626         def SizeLabelWidths(self, left, right):
627                 leftWidth = self.getLabelColumnWidth(left)
628                 rightWidth = self.getLabelColumnWidth(right)
629                 maxWidth = max(leftWidth, rightWidth)
630                 self.setLabelColumnWidth(left, maxWidth)
631                 self.setLabelColumnWidth(right, maxWidth)
632
633         def OnSize(self, e):
634                 # Make the size of the Notebook control the same size as this control
635                 self.nb.SetSize(self.GetSize())
636
637                 # Propegate the OnSize() event (just in case)
638                 e.Skip()
639
640                 # Perform out resize magic
641                 self.UpdateSize(self.printPanel)
642                 self.UpdateSize(self.advancedPanel)
643
644         def UpdateSize(self, configPanel):
645                 sizer = configPanel.GetSizer()
646
647                 # Pseudocde
648                 # if horizontal:
649                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
650                 #         switch to vertical
651                 # else:
652                 #     if width(col1) > (best_width(col1) + best_width(col1)):
653                 #         switch to horizontal
654                 #
655
656                 col1 = configPanel.leftPanel
657                 colSize1 = col1.GetSize()
658                 colBestSize1 = col1.GetBestSize()
659                 col2 = configPanel.rightPanel
660                 colSize2 = col2.GetSize()
661                 colBestSize2 = col2.GetBestSize()
662
663                 orientation = sizer.GetOrientation()
664
665                 if orientation == wx.HORIZONTAL:
666                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
667                                 configPanel.Freeze()
668                                 sizer = wx.BoxSizer(wx.VERTICAL)
669                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
670                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
671                                 configPanel.SetSizer(sizer)
672                                 #sizer.Layout()
673                                 configPanel.Layout()
674                                 self.Layout()
675                                 configPanel.Thaw()
676                 else:
677                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
678                                 configPanel.Freeze()
679                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
680                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
681                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
682                                 configPanel.SetSizer(sizer)
683                                 #sizer.Layout()
684                                 configPanel.Layout()
685                                 self.Layout()
686                                 configPanel.Thaw()
687
688         def updateProfileToControls(self):
689                 super(normalSettingsPanel, self).updateProfileToControls()
690                 if self.alterationPanel is not None:
691                         self.alterationPanel.updateProfileToControls()
692                 self.pluginPanel.updateProfileToControls()