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
198 self.normalSashPos = 320
200 if profile.getPreference('window_maximized') == 'True':
203 posx = int(profile.getPreference('window_pos_x'))
204 posy = int(profile.getPreference('window_pos_y'))
205 width = int(profile.getPreference('window_width'))
206 height = int(profile.getPreference('window_height'))
207 if posx > 0 or posy > 0:
208 self.SetPosition((posx,posy))
209 if width > 0 and height > 0:
210 self.SetSize((width,height))
212 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
216 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
218 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
221 self.updateSliceMode()
225 def updateSliceMode(self):
226 isSimple = profile.getPreference('startMode') == 'Simple'
228 self.normalSettingsPanel.Show(not isSimple)
229 self.simpleSettingsPanel.Show(isSimple)
230 self.leftPane.Layout()
232 for i in self.normalModeOnlyItems:
233 i.Enable(not isSimple)
234 self.switchToQuickprintMenuItem.Enable(not isSimple)
235 self.switchToNormalMenuItem.Enable(isSimple)
237 # Set splitter sash position & size
239 # Save normal mode sash
240 self.normalSashPos = self.splitter.GetSashPosition()
242 # Change location of sash to width of quick mode pane
243 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
244 self.splitter.SetSashPosition(width, True)
247 self.splitter.SetSashSize(0)
249 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)
258 prefDialog.Show(True)
260 def OnDropFiles(self, files):
261 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
262 profile.setPluginConfig([])
263 self.updateProfileToControls()
264 self.scene.loadScene(files)
266 def OnPrint(self, e):
267 if len(self.filelist) < 1:
268 wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
270 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
271 wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
273 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
275 def OnModelMRU(self, e):
276 fileNum = e.GetId() - self.ID_MRU_MODEL1
277 path = self.modelFileHistory.GetHistoryFile(fileNum)
279 self.modelFileHistory.AddFileToHistory(path) # move up the list
280 self.config.SetPath("/ModelMRU")
281 self.modelFileHistory.Save(self.config)
285 self.scene.loadScene(filelist)
287 def addToModelMRU(self, file):
288 self.modelFileHistory.AddFileToHistory(file)
289 self.config.SetPath("/ModelMRU")
290 self.modelFileHistory.Save(self.config)
293 def OnProfileMRU(self, e):
294 fileNum = e.GetId() - self.ID_MRU_PROFILE1
295 path = self.profileFileHistory.GetHistoryFile(fileNum)
297 self.profileFileHistory.AddFileToHistory(path) # move up the list
298 self.config.SetPath("/ProfileMRU")
299 self.profileFileHistory.Save(self.config)
302 profile.loadProfile(path)
303 self.updateProfileToControls()
305 def addToProfileMRU(self, file):
306 self.profileFileHistory.AddFileToHistory(file)
307 self.config.SetPath("/ProfileMRU")
308 self.profileFileHistory.Save(self.config)
311 def updateProfileToControls(self):
312 self.scene.updateProfileToControls()
313 self.normalSettingsPanel.updateProfileToControls()
314 self.simpleSettingsPanel.updateProfileToControls()
316 def OnLoadProfile(self, e):
317 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)
318 dlg.SetWildcard("ini files (*.ini)|*.ini")
319 if dlg.ShowModal() == wx.ID_OK:
320 profileFile = dlg.GetPath()
321 profile.loadProfile(profileFile)
322 self.updateProfileToControls()
324 # Update the Profile MRU
325 self.addToProfileMRU(profileFile)
328 def OnLoadProfileFromGcode(self, e):
329 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)
330 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
331 if dlg.ShowModal() == wx.ID_OK:
332 gcodeFile = dlg.GetPath()
333 f = open(gcodeFile, 'r')
336 if line.startswith(';CURA_PROFILE_STRING:'):
337 profile.loadProfileFromString(line[line.find(':')+1:].strip())
340 self.updateProfileToControls()
342 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)
345 def OnSaveProfile(self, e):
346 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
347 dlg.SetWildcard("ini files (*.ini)|*.ini")
348 if dlg.ShowModal() == wx.ID_OK:
349 profileFile = dlg.GetPath()
350 profile.saveProfile(profileFile)
353 def OnResetProfile(self, e):
354 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)
355 result = dlg.ShowModal() == wx.ID_YES
358 profile.resetProfile()
359 self.updateProfileToControls()
361 def OnBatchRun(self, e):
362 br = batchRun.batchRunWindow(self)
366 def OnSimpleSwitch(self, e):
367 profile.putPreference('startMode', 'Simple')
368 self.updateSliceMode()
370 def OnNormalSwitch(self, e):
371 profile.putPreference('startMode', 'Normal')
372 self.updateSliceMode()
374 def OnDefaultMarlinFirmware(self, e):
375 firmwareInstall.InstallFirmware()
377 def OnCustomFirmware(self, e):
378 if profile.getPreference('machine_type') == 'ultimaker':
379 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)
380 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
381 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
382 if dlg.ShowModal() == wx.ID_OK:
383 filename = dlg.GetPath()
384 if not(os.path.exists(filename)):
386 #For some reason my Ubuntu 10.10 crashes here.
387 firmwareInstall.InstallFirmware(filename)
389 def OnFirstRunWizard(self, e):
390 configWizard.configWizard()
391 self.updateProfileToControls()
393 def OnBedLevelWizard(self, e):
394 configWizard.bedLevelWizard()
396 def OnExpertOpen(self, e):
397 ecw = expertConfig.expertConfigWindow()
401 def OnProjectPlanner(self, e):
402 pp = projectPlanner.projectPlanner()
406 def OnMinecraftImport(self, e):
407 mi = minecraftImport.minecraftImportWindow(self)
411 def OnCheckForUpdate(self, e):
412 newVersion = version.checkForNewerVersion()
413 if newVersion is not None:
414 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:
415 webbrowser.open(newVersion)
417 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
419 def OnClose(self, e):
420 profile.saveProfile(profile.getDefaultProfilePath())
422 # Save the window position, size & state from the preferences file
423 profile.putPreference('window_maximized', self.IsMaximized())
424 if not self.IsMaximized() and not self.IsIconized():
425 (posx, posy) = self.GetPosition()
426 profile.putPreference('window_pos_x', posx)
427 profile.putPreference('window_pos_y', posy)
428 (width, height) = self.GetSize()
429 profile.putPreference('window_width', width)
430 profile.putPreference('window_height', height)
432 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
433 isSimple = profile.getPreference('startMode') == 'Simple'
435 self.normalSashPos = self.splitter.GetSashPosition()
436 profile.putPreference('window_normal_sash', self.normalSashPos)
438 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
440 self.scene.OnPaint = lambda e : e
446 class normalSettingsPanel(configBase.configPanelBase):
447 "Main user interface window"
448 def __init__(self, parent, callback = None):
449 super(normalSettingsPanel, self).__init__(parent, callback)
452 self.nb = wx.Notebook(self)
453 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
454 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
456 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
457 self._addSettingsToPanels('basic', left, right)
458 self.SizeLabelWidths(left, right)
460 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
461 self._addSettingsToPanels('advanced', left, right)
462 self.SizeLabelWidths(left, right)
465 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
466 if len(self.pluginPanel.pluginList) > 0:
467 self.nb.AddPage(self.pluginPanel, "Plugins")
469 self.pluginPanel.Show(False)
472 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
473 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
475 self.Bind(wx.EVT_SIZE, self.OnSize)
477 def _addSettingsToPanels(self, category, left, right):
478 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
482 for title in profile.getSubCategoriesFor(category):
483 n += 1 + len(profile.getSettingsForCategory(category, title))
486 configBase.TitleRow(p, title)
487 for s in profile.getSettingsForCategory(category, title):
488 if s.checkConditions():
489 configBase.SettingRow(p, s.getName())
491 def SizeLabelWidths(self, left, right):
492 leftWidth = self.getLabelColumnWidth(left)
493 rightWidth = self.getLabelColumnWidth(right)
494 maxWidth = max(leftWidth, rightWidth)
495 self.setLabelColumnWidth(left, maxWidth)
496 self.setLabelColumnWidth(right, maxWidth)
499 # Make the size of the Notebook control the same size as this control
500 self.nb.SetSize(self.GetSize())
502 # Propegate the OnSize() event (just in case)
505 # Perform out resize magic
506 self.UpdateSize(self.printPanel)
507 self.UpdateSize(self.advancedPanel)
509 def UpdateSize(self, configPanel):
510 sizer = configPanel.GetSizer()
514 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
517 # if width(col1) > (best_width(col1) + best_width(col1)):
518 # switch to horizontal
521 col1 = configPanel.leftPanel
522 colSize1 = col1.GetSize()
523 colBestSize1 = col1.GetBestSize()
524 col2 = configPanel.rightPanel
525 colSize2 = col2.GetSize()
526 colBestSize2 = col2.GetBestSize()
528 orientation = sizer.GetOrientation()
530 if orientation == wx.HORIZONTAL:
531 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
533 sizer = wx.BoxSizer(wx.VERTICAL)
534 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
535 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
536 configPanel.SetSizer(sizer)
542 if colSize1[0] > (colBestSize1[0] + colBestSize2[0]):
544 sizer = wx.BoxSizer(wx.HORIZONTAL)
545 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
546 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
547 configPanel.SetSizer(sizer)
553 def updateProfileToControls(self):
554 super(normalSettingsPanel, self).updateProfileToControls()
555 self.alterationPanel.updateProfileToControls()
556 self.pluginPanel.updateProfileToControls()