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 printWindow
15 from Cura.gui import simpleMode
16 from Cura.gui import projectPlanner
17 from Cura.gui import sceneView
18 from Cura.gui.tools import batchRun
19 from Cura.gui.util import dropTarget
20 from Cura.gui.tools import minecraftImport
21 from Cura.util import profile
22 from Cura.util import version
23 from Cura.util import sliceRun
24 from Cura.util import meshLoader
26 class mainWindow(wx.Frame):
28 super(mainWindow, self).__init__(None, title='Cura Steam Engine BETA - ' + version.getVersion())
30 self.extruderCount = int(profile.getPreference('extruder_amount'))
32 wx.EVT_CLOSE(self, self.OnClose)
34 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
36 self.normalModeOnlyItems = []
38 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
39 self.config = wx.FileConfig(appName="Cura",
40 localFilename=mruFile,
41 style=wx.CONFIG_USE_LOCAL_FILE)
43 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)]
44 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
45 self.config.SetPath("/ModelMRU")
46 self.modelFileHistory.Load(self.config)
48 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)]
49 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
50 self.config.SetPath("/ProfileMRU")
51 self.profileFileHistory.Load(self.config)
53 self.menubar = wx.MenuBar()
54 self.fileMenu = wx.Menu()
55 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
56 self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowLoadModel(), i)
57 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
58 self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowPrintWindow(), i)
60 self.fileMenu.AppendSeparator()
61 i = self.fileMenu.Append(-1, 'Open Profile...')
62 self.normalModeOnlyItems.append(i)
63 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
64 i = self.fileMenu.Append(-1, 'Save Profile...')
65 self.normalModeOnlyItems.append(i)
66 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
67 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
68 self.normalModeOnlyItems.append(i)
69 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
70 self.fileMenu.AppendSeparator()
71 i = self.fileMenu.Append(-1, 'Reset Profile to default')
72 self.normalModeOnlyItems.append(i)
73 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
75 self.fileMenu.AppendSeparator()
76 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
77 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
78 self.fileMenu.AppendSeparator()
81 modelHistoryMenu = wx.Menu()
82 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
83 self.modelFileHistory.UseMenu(modelHistoryMenu)
84 self.modelFileHistory.AddFilesToMenu()
85 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
88 profileHistoryMenu = wx.Menu()
89 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
90 self.profileFileHistory.UseMenu(profileHistoryMenu)
91 self.profileFileHistory.AddFilesToMenu()
92 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
94 self.fileMenu.AppendSeparator()
95 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
96 self.Bind(wx.EVT_MENU, self.OnQuit, i)
97 self.menubar.Append(self.fileMenu, '&File')
100 i = toolsMenu.Append(-1, 'Switch to quickprint...')
101 self.switchToQuickprintMenuItem = i
102 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
103 i = toolsMenu.Append(-1, 'Switch to full settings...')
104 self.switchToNormalMenuItem = i
105 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
106 toolsMenu.AppendSeparator()
107 i = toolsMenu.Append(-1, 'Project planner...')
108 self.Bind(wx.EVT_MENU, self.OnProjectPlanner, i)
109 self.normalModeOnlyItems.append(i)
110 i = toolsMenu.Append(-1, 'Batch run...')
111 self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
112 self.normalModeOnlyItems.append(i)
113 if minecraftImport.hasMinecraft():
114 i = toolsMenu.Append(-1, 'Minecraft import...')
115 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
116 self.menubar.Append(toolsMenu, 'Tools')
118 expertMenu = wx.Menu()
119 i = expertMenu.Append(-1, 'Open expert settings...')
120 self.normalModeOnlyItems.append(i)
121 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
122 expertMenu.AppendSeparator()
123 if firmwareInstall.getDefaultFirmware() is not None:
124 i = expertMenu.Append(-1, 'Install default Marlin firmware')
125 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
126 i = expertMenu.Append(-1, 'Install custom firmware')
127 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
128 expertMenu.AppendSeparator()
129 i = expertMenu.Append(-1, 'Run first run wizard...')
130 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
131 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
132 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
133 self.menubar.Append(expertMenu, 'Expert')
136 i = helpMenu.Append(-1, 'Online documentation...')
137 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
138 i = helpMenu.Append(-1, 'Report a problem...')
139 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
140 i = helpMenu.Append(-1, 'Check for update...')
141 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
142 self.menubar.Append(helpMenu, 'Help')
143 self.SetMenuBar(self.menubar)
145 if profile.getPreference('lastFile') != '':
146 self.filelist = profile.getPreference('lastFile').split(';')
150 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
151 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
152 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
153 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
156 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane)
157 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
159 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
160 self.leftSizer.Add(self.simpleSettingsPanel)
161 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
162 self.leftPane.SetSizer(self.leftSizer)
165 self.scene = sceneView.SceneView(self.rightPane)
167 #Main sizer, to position the preview window, buttons and tab control
168 sizer = wx.BoxSizer()
169 self.rightPane.SetSizer(sizer)
170 sizer.Add(self.scene, 1, flag=wx.EXPAND)
173 sizer = wx.BoxSizer(wx.VERTICAL)
175 sizer.Add(self.splitter, 1, wx.EXPAND)
179 if len(self.filelist) > 0:
180 self.scene.loadScene(self.filelist)
182 # Update the Model MRU
183 for idx in xrange(0, len(self.filelist)):
184 self.addToModelMRU(self.filelist[idx])
186 self.updateProfileToControls()
188 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
190 self.simpleSettingsPanel.Show(False)
191 self.normalSettingsPanel.Show(False)
193 # Set default window size & position
194 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
197 # Restore the window position, size & state from the preferences file
199 if profile.getPreference('window_maximized') == 'True':
202 posx = int(profile.getPreference('window_pos_x'))
203 posy = int(profile.getPreference('window_pos_y'))
204 width = int(profile.getPreference('window_width'))
205 height = int(profile.getPreference('window_height'))
206 if posx > 0 or posy > 0:
207 self.SetPosition((posx,posy))
208 if width > 0 and height > 0:
209 self.SetSize((width,height))
211 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
212 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
213 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
217 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
219 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
221 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
223 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
224 self.SetSize((800,600))
227 self.updateSliceMode()
231 def updateSliceMode(self):
232 isSimple = profile.getPreference('startMode') == 'Simple'
234 self.normalSettingsPanel.Show(not isSimple)
235 self.simpleSettingsPanel.Show(isSimple)
236 self.leftPane.Layout()
238 for i in self.normalModeOnlyItems:
239 i.Enable(not isSimple)
240 self.switchToQuickprintMenuItem.Enable(not isSimple)
241 self.switchToNormalMenuItem.Enable(isSimple)
243 # Set splitter sash position & size
245 # Save normal mode sash
246 self.normalSashPos = self.splitter.GetSashPosition()
248 # Change location of sash to width of quick mode pane
249 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
250 self.splitter.SetSashPosition(width, True)
253 self.splitter.SetSashSize(0)
255 self.splitter.SetSashPosition(self.normalSashPos, True)
257 self.splitter.SetSashSize(4)
258 self.scene.updateProfileToControls()
260 def OnPreferences(self, e):
261 prefDialog = preferencesDialog.preferencesDialog(self)
263 prefDialog.Show(True)
265 def OnDropFiles(self, files):
266 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
267 profile.setPluginConfig([])
268 self.updateProfileToControls()
269 self.scene.loadScene(files)
271 def OnPrint(self, e):
272 if len(self.filelist) < 1:
273 wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
275 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
276 wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
278 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
280 def OnModelMRU(self, e):
281 fileNum = e.GetId() - self.ID_MRU_MODEL1
282 path = self.modelFileHistory.GetHistoryFile(fileNum)
284 self.modelFileHistory.AddFileToHistory(path) # move up the list
285 self.config.SetPath("/ModelMRU")
286 self.modelFileHistory.Save(self.config)
290 self.scene.loadScene(filelist)
292 def addToModelMRU(self, file):
293 self.modelFileHistory.AddFileToHistory(file)
294 self.config.SetPath("/ModelMRU")
295 self.modelFileHistory.Save(self.config)
298 def OnProfileMRU(self, e):
299 fileNum = e.GetId() - self.ID_MRU_PROFILE1
300 path = self.profileFileHistory.GetHistoryFile(fileNum)
302 self.profileFileHistory.AddFileToHistory(path) # move up the list
303 self.config.SetPath("/ProfileMRU")
304 self.profileFileHistory.Save(self.config)
307 profile.loadProfile(path)
308 self.updateProfileToControls()
310 def addToProfileMRU(self, file):
311 self.profileFileHistory.AddFileToHistory(file)
312 self.config.SetPath("/ProfileMRU")
313 self.profileFileHistory.Save(self.config)
316 def updateProfileToControls(self):
317 self.scene.updateProfileToControls()
318 self.normalSettingsPanel.updateProfileToControls()
319 self.simpleSettingsPanel.updateProfileToControls()
321 def OnLoadProfile(self, e):
322 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)
323 dlg.SetWildcard("ini files (*.ini)|*.ini")
324 if dlg.ShowModal() == wx.ID_OK:
325 profileFile = dlg.GetPath()
326 profile.loadProfile(profileFile)
327 self.updateProfileToControls()
329 # Update the Profile MRU
330 self.addToProfileMRU(profileFile)
333 def OnLoadProfileFromGcode(self, e):
334 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)
335 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
336 if dlg.ShowModal() == wx.ID_OK:
337 gcodeFile = dlg.GetPath()
338 f = open(gcodeFile, 'r')
341 if line.startswith(';CURA_PROFILE_STRING:'):
342 profile.loadProfileFromString(line[line.find(':')+1:].strip())
345 self.updateProfileToControls()
347 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)
350 def OnSaveProfile(self, e):
351 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
352 dlg.SetWildcard("ini files (*.ini)|*.ini")
353 if dlg.ShowModal() == wx.ID_OK:
354 profileFile = dlg.GetPath()
355 profile.saveProfile(profileFile)
358 def OnResetProfile(self, e):
359 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)
360 result = dlg.ShowModal() == wx.ID_YES
363 profile.resetProfile()
364 self.updateProfileToControls()
366 def OnBatchRun(self, e):
367 br = batchRun.batchRunWindow(self)
371 def OnSimpleSwitch(self, e):
372 profile.putPreference('startMode', 'Simple')
373 self.updateSliceMode()
375 def OnNormalSwitch(self, e):
376 profile.putPreference('startMode', 'Normal')
377 self.updateSliceMode()
379 def OnDefaultMarlinFirmware(self, e):
380 firmwareInstall.InstallFirmware()
382 def OnCustomFirmware(self, e):
383 if profile.getPreference('machine_type') == 'ultimaker':
384 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)
385 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
386 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
387 if dlg.ShowModal() == wx.ID_OK:
388 filename = dlg.GetPath()
389 if not(os.path.exists(filename)):
391 #For some reason my Ubuntu 10.10 crashes here.
392 firmwareInstall.InstallFirmware(filename)
394 def OnFirstRunWizard(self, e):
395 configWizard.configWizard()
396 self.updateProfileToControls()
398 def OnBedLevelWizard(self, e):
399 configWizard.bedLevelWizard()
401 def OnExpertOpen(self, e):
402 ecw = expertConfig.expertConfigWindow()
406 def OnProjectPlanner(self, e):
407 pp = projectPlanner.projectPlanner()
411 def OnMinecraftImport(self, e):
412 mi = minecraftImport.minecraftImportWindow(self)
416 def OnCheckForUpdate(self, e):
417 newVersion = version.checkForNewerVersion()
418 if newVersion is not None:
419 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:
420 webbrowser.open(newVersion)
422 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
424 def OnClose(self, e):
425 profile.saveProfile(profile.getDefaultProfilePath())
427 # Save the window position, size & state from the preferences file
428 profile.putPreference('window_maximized', self.IsMaximized())
429 if not self.IsMaximized() and not self.IsIconized():
430 (posx, posy) = self.GetPosition()
431 profile.putPreference('window_pos_x', posx)
432 profile.putPreference('window_pos_y', posy)
433 (width, height) = self.GetSize()
434 profile.putPreference('window_width', width)
435 profile.putPreference('window_height', height)
437 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
438 isSimple = profile.getPreference('startMode') == 'Simple'
440 self.normalSashPos = self.splitter.GetSashPosition()
441 profile.putPreference('window_normal_sash', self.normalSashPos)
443 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
445 self.scene.OnPaint = lambda e : e
451 class normalSettingsPanel(configBase.configPanelBase):
452 "Main user interface window"
453 def __init__(self, parent, callback = None):
454 super(normalSettingsPanel, self).__init__(parent, callback)
457 self.nb = wx.Notebook(self)
458 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
459 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
461 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
462 self._addSettingsToPanels('basic', left, right)
463 self.SizeLabelWidths(left, right)
465 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
466 self._addSettingsToPanels('advanced', left, right)
467 self.SizeLabelWidths(left, right)
470 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
471 if len(self.pluginPanel.pluginList) > 0:
472 self.nb.AddPage(self.pluginPanel, "Plugins")
474 self.pluginPanel.Show(False)
477 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
478 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
480 self.Bind(wx.EVT_SIZE, self.OnSize)
482 self.nb.SetSize(self.GetSize())
483 self.UpdateSize(self.printPanel)
484 self.UpdateSize(self.advancedPanel)
486 def _addSettingsToPanels(self, category, left, right):
487 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
491 for title in profile.getSubCategoriesFor(category):
492 n += 1 + len(profile.getSettingsForCategory(category, title))
495 configBase.TitleRow(p, title)
496 for s in profile.getSettingsForCategory(category, title):
497 if s.checkConditions():
498 configBase.SettingRow(p, s.getName())
500 def SizeLabelWidths(self, left, right):
501 leftWidth = self.getLabelColumnWidth(left)
502 rightWidth = self.getLabelColumnWidth(right)
503 maxWidth = max(leftWidth, rightWidth)
504 self.setLabelColumnWidth(left, maxWidth)
505 self.setLabelColumnWidth(right, maxWidth)
508 # Make the size of the Notebook control the same size as this control
509 self.nb.SetSize(self.GetSize())
511 # Propegate the OnSize() event (just in case)
514 # Perform out resize magic
515 self.UpdateSize(self.printPanel)
516 self.UpdateSize(self.advancedPanel)
518 def UpdateSize(self, configPanel):
519 sizer = configPanel.GetSizer()
523 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
526 # if width(col1) > (best_width(col1) + best_width(col1)):
527 # switch to horizontal
530 col1 = configPanel.leftPanel
531 colSize1 = col1.GetSize()
532 colBestSize1 = col1.GetBestSize()
533 col2 = configPanel.rightPanel
534 colSize2 = col2.GetSize()
535 colBestSize2 = col2.GetBestSize()
537 orientation = sizer.GetOrientation()
539 if orientation == wx.HORIZONTAL:
540 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
542 sizer = wx.BoxSizer(wx.VERTICAL)
543 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
544 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
545 configPanel.SetSizer(sizer)
551 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
553 sizer = wx.BoxSizer(wx.HORIZONTAL)
554 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
555 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
556 configPanel.SetSizer(sizer)
562 def updateProfileToControls(self):
563 super(normalSettingsPanel, self).updateProfileToControls()
564 self.alterationPanel.updateProfileToControls()
565 self.pluginPanel.updateProfileToControls()