1 from __future__ import absolute_import
7 from Cura.gui import configBase
8 from Cura.gui import expertConfig
9 from Cura.gui import alterationPanel
10 from Cura.gui import pluginPanel
11 from Cura.gui import preferencesDialog
12 from Cura.gui import configWizard
13 from Cura.gui import firmwareInstall
14 from Cura.gui import simpleMode
15 from Cura.gui import sceneView
16 from Cura.gui.tools import batchRun
17 from Cura.gui.util import dropTarget
18 from Cura.gui.tools import minecraftImport
19 from Cura.util import profile
20 from Cura.util import version
21 from Cura.util import meshLoader
23 class mainWindow(wx.Frame):
25 super(mainWindow, self).__init__(None, title='Cura Steam Engine BETA - ' + version.getVersion())
27 self.extruderCount = int(profile.getPreference('extruder_amount'))
29 wx.EVT_CLOSE(self, self.OnClose)
31 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
33 self.normalModeOnlyItems = []
35 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
36 self.config = wx.FileConfig(appName="Cura",
37 localFilename=mruFile,
38 style=wx.CONFIG_USE_LOCAL_FILE)
40 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)]
41 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
42 self.config.SetPath("/ModelMRU")
43 self.modelFileHistory.Load(self.config)
45 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)]
46 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
47 self.config.SetPath("/ProfileMRU")
48 self.profileFileHistory.Load(self.config)
50 self.menubar = wx.MenuBar()
51 self.fileMenu = wx.Menu()
52 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
53 self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowLoadModel(1), i)
54 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
55 self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowPrintWindow(), i)
57 self.fileMenu.AppendSeparator()
58 i = self.fileMenu.Append(-1, 'Open Profile...')
59 self.normalModeOnlyItems.append(i)
60 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
61 i = self.fileMenu.Append(-1, 'Save Profile...')
62 self.normalModeOnlyItems.append(i)
63 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
64 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
65 self.normalModeOnlyItems.append(i)
66 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
67 self.fileMenu.AppendSeparator()
68 i = self.fileMenu.Append(-1, 'Reset Profile to default')
69 self.normalModeOnlyItems.append(i)
70 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
72 self.fileMenu.AppendSeparator()
73 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
74 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
75 self.fileMenu.AppendSeparator()
78 modelHistoryMenu = wx.Menu()
79 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
80 self.modelFileHistory.UseMenu(modelHistoryMenu)
81 self.modelFileHistory.AddFilesToMenu()
82 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
85 profileHistoryMenu = wx.Menu()
86 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
87 self.profileFileHistory.UseMenu(profileHistoryMenu)
88 self.profileFileHistory.AddFilesToMenu()
89 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
91 self.fileMenu.AppendSeparator()
92 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
93 self.Bind(wx.EVT_MENU, self.OnQuit, i)
94 self.menubar.Append(self.fileMenu, '&File')
97 i = toolsMenu.Append(-1, 'Switch to quickprint...')
98 self.switchToQuickprintMenuItem = i
99 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
100 i = toolsMenu.Append(-1, 'Switch to full settings...')
101 self.switchToNormalMenuItem = i
102 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
103 toolsMenu.AppendSeparator()
104 #i = toolsMenu.Append(-1, 'Batch run...')
105 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
106 #self.normalModeOnlyItems.append(i)
107 if minecraftImport.hasMinecraft():
108 i = toolsMenu.Append(-1, 'Minecraft import...')
109 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
110 self.menubar.Append(toolsMenu, 'Tools')
112 expertMenu = wx.Menu()
113 i = expertMenu.Append(-1, 'Open expert settings...')
114 self.normalModeOnlyItems.append(i)
115 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
116 expertMenu.AppendSeparator()
117 if firmwareInstall.getDefaultFirmware() is not None:
118 i = expertMenu.Append(-1, 'Install default Marlin firmware')
119 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
120 i = expertMenu.Append(-1, 'Install custom firmware')
121 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
122 expertMenu.AppendSeparator()
123 i = expertMenu.Append(-1, 'Run first run wizard...')
124 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
125 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
126 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
127 self.menubar.Append(expertMenu, 'Expert')
130 i = helpMenu.Append(-1, 'Online documentation...')
131 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
132 i = helpMenu.Append(-1, 'Report a problem...')
133 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
134 i = helpMenu.Append(-1, 'Check for update...')
135 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
136 self.menubar.Append(helpMenu, 'Help')
137 self.SetMenuBar(self.menubar)
139 if profile.getPreference('lastFile') != '':
140 self.filelist = profile.getPreference('lastFile').split(';')
144 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
145 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
146 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
147 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
150 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
151 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
153 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
154 self.leftSizer.Add(self.simpleSettingsPanel)
155 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
156 self.leftPane.SetSizer(self.leftSizer)
159 self.scene = sceneView.SceneView(self.rightPane)
161 #Main sizer, to position the preview window, buttons and tab control
162 sizer = wx.BoxSizer()
163 self.rightPane.SetSizer(sizer)
164 sizer.Add(self.scene, 1, flag=wx.EXPAND)
167 sizer = wx.BoxSizer(wx.VERTICAL)
169 sizer.Add(self.splitter, 1, wx.EXPAND)
173 if len(self.filelist) > 0:
174 self.scene.loadScene(self.filelist)
176 # Update the Model MRU
177 for idx in xrange(0, len(self.filelist)):
178 self.addToModelMRU(self.filelist[idx])
180 self.updateProfileToControls()
182 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
184 self.simpleSettingsPanel.Show(False)
185 self.normalSettingsPanel.Show(False)
187 # Set default window size & position
188 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
191 # Restore the window position, size & state from the preferences file
193 if profile.getPreference('window_maximized') == 'True':
196 posx = int(profile.getPreference('window_pos_x'))
197 posy = int(profile.getPreference('window_pos_y'))
198 width = int(profile.getPreference('window_width'))
199 height = int(profile.getPreference('window_height'))
200 if posx > 0 or posy > 0:
201 self.SetPosition((posx,posy))
202 if width > 0 and height > 0:
203 self.SetSize((width,height))
205 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
207 self.normalSashPos = 0
209 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
210 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
212 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
214 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
216 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
218 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
219 self.SetSize((800,600))
222 self.updateSliceMode()
226 def updateSliceMode(self):
227 isSimple = profile.getPreference('startMode') == 'Simple'
229 self.normalSettingsPanel.Show(not isSimple)
230 self.simpleSettingsPanel.Show(isSimple)
231 self.leftPane.Layout()
233 for i in self.normalModeOnlyItems:
234 i.Enable(not isSimple)
235 self.switchToQuickprintMenuItem.Enable(not isSimple)
236 self.switchToNormalMenuItem.Enable(isSimple)
238 # Set splitter sash position & size
240 # Save normal mode sash
241 self.normalSashPos = self.splitter.GetSashPosition()
243 # Change location of sash to width of quick mode pane
244 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
245 self.splitter.SetSashPosition(width, True)
248 self.splitter.SetSashSize(0)
250 self.splitter.SetSashPosition(self.normalSashPos, True)
252 self.splitter.SetSashSize(4)
253 self.scene.updateProfileToControls()
255 def OnPreferences(self, e):
256 prefDialog = preferencesDialog.preferencesDialog(self)
260 def OnDropFiles(self, files):
261 profile.setPluginConfig([])
262 self.updateProfileToControls()
263 self.scene.loadScene(files)
265 def OnModelMRU(self, e):
266 fileNum = e.GetId() - self.ID_MRU_MODEL1
267 path = self.modelFileHistory.GetHistoryFile(fileNum)
269 self.modelFileHistory.AddFileToHistory(path) # move up the list
270 self.config.SetPath("/ModelMRU")
271 self.modelFileHistory.Save(self.config)
275 self.scene.loadScene(filelist)
277 def addToModelMRU(self, file):
278 self.modelFileHistory.AddFileToHistory(file)
279 self.config.SetPath("/ModelMRU")
280 self.modelFileHistory.Save(self.config)
283 def OnProfileMRU(self, e):
284 fileNum = e.GetId() - self.ID_MRU_PROFILE1
285 path = self.profileFileHistory.GetHistoryFile(fileNum)
287 self.profileFileHistory.AddFileToHistory(path) # move up the list
288 self.config.SetPath("/ProfileMRU")
289 self.profileFileHistory.Save(self.config)
292 profile.loadProfile(path)
293 self.updateProfileToControls()
295 def addToProfileMRU(self, file):
296 self.profileFileHistory.AddFileToHistory(file)
297 self.config.SetPath("/ProfileMRU")
298 self.profileFileHistory.Save(self.config)
301 def updateProfileToControls(self):
302 self.scene.updateProfileToControls()
303 self.normalSettingsPanel.updateProfileToControls()
304 self.simpleSettingsPanel.updateProfileToControls()
306 def OnLoadProfile(self, e):
307 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)
308 dlg.SetWildcard("ini files (*.ini)|*.ini")
309 if dlg.ShowModal() == wx.ID_OK:
310 profileFile = dlg.GetPath()
311 profile.loadProfile(profileFile)
312 self.updateProfileToControls()
314 # Update the Profile MRU
315 self.addToProfileMRU(profileFile)
318 def OnLoadProfileFromGcode(self, e):
319 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)
320 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
321 if dlg.ShowModal() == wx.ID_OK:
322 gcodeFile = dlg.GetPath()
323 f = open(gcodeFile, 'r')
326 if line.startswith(';CURA_PROFILE_STRING:'):
327 profile.loadProfileFromString(line[line.find(':')+1:].strip())
330 self.updateProfileToControls()
332 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)
335 def OnSaveProfile(self, e):
336 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
337 dlg.SetWildcard("ini files (*.ini)|*.ini")
338 if dlg.ShowModal() == wx.ID_OK:
339 profileFile = dlg.GetPath()
340 profile.saveProfile(profileFile)
343 def OnResetProfile(self, e):
344 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)
345 result = dlg.ShowModal() == wx.ID_YES
348 profile.resetProfile()
349 self.updateProfileToControls()
351 def OnBatchRun(self, e):
352 br = batchRun.batchRunWindow(self)
356 def OnSimpleSwitch(self, e):
357 profile.putPreference('startMode', 'Simple')
358 self.updateSliceMode()
360 def OnNormalSwitch(self, e):
361 profile.putPreference('startMode', 'Normal')
362 self.updateSliceMode()
364 def OnDefaultMarlinFirmware(self, e):
365 firmwareInstall.InstallFirmware()
367 def OnCustomFirmware(self, e):
368 if profile.getPreference('machine_type') == 'ultimaker':
369 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)
370 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
371 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
372 if dlg.ShowModal() == wx.ID_OK:
373 filename = dlg.GetPath()
374 if not(os.path.exists(filename)):
376 #For some reason my Ubuntu 10.10 crashes here.
377 firmwareInstall.InstallFirmware(filename)
379 def OnFirstRunWizard(self, e):
380 configWizard.configWizard()
381 self.updateProfileToControls()
383 def OnBedLevelWizard(self, e):
384 configWizard.bedLevelWizard()
386 def OnExpertOpen(self, e):
387 ecw = expertConfig.expertConfigWindow()
390 self.scene.sceneUpdated()
392 def OnMinecraftImport(self, e):
393 mi = minecraftImport.minecraftImportWindow(self)
397 def OnCheckForUpdate(self, e):
398 newVersion = version.checkForNewerVersion()
399 if newVersion is not None:
400 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:
401 webbrowser.open(newVersion)
403 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
405 def OnClose(self, e):
406 profile.saveProfile(profile.getDefaultProfilePath())
408 # Save the window position, size & state from the preferences file
409 profile.putPreference('window_maximized', self.IsMaximized())
410 if not self.IsMaximized() and not self.IsIconized():
411 (posx, posy) = self.GetPosition()
412 profile.putPreference('window_pos_x', posx)
413 profile.putPreference('window_pos_y', posy)
414 (width, height) = self.GetSize()
415 profile.putPreference('window_width', width)
416 profile.putPreference('window_height', height)
418 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
419 isSimple = profile.getPreference('startMode') == 'Simple'
421 self.normalSashPos = self.splitter.GetSashPosition()
422 profile.putPreference('window_normal_sash', self.normalSashPos)
424 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
426 self.scene.OnPaint = lambda e : e
427 self.scene._slicer.cleanup()
433 class normalSettingsPanel(configBase.configPanelBase):
434 "Main user interface window"
435 def __init__(self, parent, callback = None):
436 super(normalSettingsPanel, self).__init__(parent, callback)
439 self.nb = wx.Notebook(self)
440 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
441 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
443 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
444 self._addSettingsToPanels('basic', left, right)
445 self.SizeLabelWidths(left, right)
447 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
448 self._addSettingsToPanels('advanced', left, right)
449 self.SizeLabelWidths(left, right)
452 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
453 if len(self.pluginPanel.pluginList) > 0 and False:
454 self.nb.AddPage(self.pluginPanel, "Plugins")
456 self.pluginPanel.Show(False)
459 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
460 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
462 self.Bind(wx.EVT_SIZE, self.OnSize)
464 self.nb.SetSize(self.GetSize())
465 self.UpdateSize(self.printPanel)
466 self.UpdateSize(self.advancedPanel)
468 def _addSettingsToPanels(self, category, left, right):
469 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
473 for title in profile.getSubCategoriesFor(category):
474 n += 1 + len(profile.getSettingsForCategory(category, title))
477 configBase.TitleRow(p, title)
478 for s in profile.getSettingsForCategory(category, title):
479 if s.checkConditions():
480 configBase.SettingRow(p, s.getName())
482 def SizeLabelWidths(self, left, right):
483 leftWidth = self.getLabelColumnWidth(left)
484 rightWidth = self.getLabelColumnWidth(right)
485 maxWidth = max(leftWidth, rightWidth)
486 self.setLabelColumnWidth(left, maxWidth)
487 self.setLabelColumnWidth(right, maxWidth)
490 # Make the size of the Notebook control the same size as this control
491 self.nb.SetSize(self.GetSize())
493 # Propegate the OnSize() event (just in case)
496 # Perform out resize magic
497 self.UpdateSize(self.printPanel)
498 self.UpdateSize(self.advancedPanel)
500 def UpdateSize(self, configPanel):
501 sizer = configPanel.GetSizer()
505 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
508 # if width(col1) > (best_width(col1) + best_width(col1)):
509 # switch to horizontal
512 col1 = configPanel.leftPanel
513 colSize1 = col1.GetSize()
514 colBestSize1 = col1.GetBestSize()
515 col2 = configPanel.rightPanel
516 colSize2 = col2.GetSize()
517 colBestSize2 = col2.GetBestSize()
519 orientation = sizer.GetOrientation()
521 if orientation == wx.HORIZONTAL:
522 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
524 sizer = wx.BoxSizer(wx.VERTICAL)
525 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
526 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
527 configPanel.SetSizer(sizer)
533 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
535 sizer = wx.BoxSizer(wx.HORIZONTAL)
536 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
537 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
538 configPanel.SetSizer(sizer)
544 def updateProfileToControls(self):
545 super(normalSettingsPanel, self).updateProfileToControls()
546 self.alterationPanel.updateProfileToControls()
547 self.pluginPanel.updateProfileToControls()