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:
220 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
222 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
223 self.SetSize((800,600))
226 self.updateSliceMode()
230 def updateSliceMode(self):
231 isSimple = profile.getPreference('startMode') == 'Simple'
233 self.normalSettingsPanel.Show(not isSimple)
234 self.simpleSettingsPanel.Show(isSimple)
235 self.leftPane.Layout()
237 for i in self.normalModeOnlyItems:
238 i.Enable(not isSimple)
239 self.switchToQuickprintMenuItem.Enable(not isSimple)
240 self.switchToNormalMenuItem.Enable(isSimple)
242 # Set splitter sash position & size
244 # Save normal mode sash
245 self.normalSashPos = self.splitter.GetSashPosition()
247 # Change location of sash to width of quick mode pane
248 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
249 self.splitter.SetSashPosition(width, True)
252 self.splitter.SetSashSize(0)
254 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 def _addSettingsToPanels(self, category, left, right):
483 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
487 for title in profile.getSubCategoriesFor(category):
488 n += 1 + len(profile.getSettingsForCategory(category, title))
491 configBase.TitleRow(p, title)
492 for s in profile.getSettingsForCategory(category, title):
493 if s.checkConditions():
494 configBase.SettingRow(p, s.getName())
496 def SizeLabelWidths(self, left, right):
497 leftWidth = self.getLabelColumnWidth(left)
498 rightWidth = self.getLabelColumnWidth(right)
499 maxWidth = max(leftWidth, rightWidth)
500 self.setLabelColumnWidth(left, maxWidth)
501 self.setLabelColumnWidth(right, maxWidth)
504 # Make the size of the Notebook control the same size as this control
505 self.nb.SetSize(self.GetSize())
507 # Propegate the OnSize() event (just in case)
510 # Perform out resize magic
511 self.UpdateSize(self.printPanel)
512 self.UpdateSize(self.advancedPanel)
514 def UpdateSize(self, configPanel):
515 sizer = configPanel.GetSizer()
519 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
522 # if width(col1) > (best_width(col1) + best_width(col1)):
523 # switch to horizontal
526 col1 = configPanel.leftPanel
527 colSize1 = col1.GetSize()
528 colBestSize1 = col1.GetBestSize()
529 col2 = configPanel.rightPanel
530 colSize2 = col2.GetSize()
531 colBestSize2 = col2.GetBestSize()
533 orientation = sizer.GetOrientation()
535 if orientation == wx.HORIZONTAL:
536 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
538 sizer = wx.BoxSizer(wx.VERTICAL)
539 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
540 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
541 configPanel.SetSizer(sizer)
547 if colSize1[0] > (colBestSize1[0] + colBestSize2[0]):
549 sizer = wx.BoxSizer(wx.HORIZONTAL)
550 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
551 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
552 configPanel.SetSizer(sizer)
558 def updateProfileToControls(self):
559 super(normalSettingsPanel, self).updateProfileToControls()
560 self.alterationPanel.updateProfileToControls()
561 self.pluginPanel.updateProfileToControls()