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.gui.tools import superformula
20 from Cura.util import profile
21 from Cura.util import version
22 from Cura.util import meshLoader
24 class mainWindow(wx.Frame):
26 super(mainWindow, self).__init__(None, title='Cura Steam Engine BETA - ' + version.getVersion())
28 self.extruderCount = int(profile.getPreference('extruder_amount'))
30 wx.EVT_CLOSE(self, self.OnClose)
32 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
34 self.normalModeOnlyItems = []
36 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
37 self.config = wx.FileConfig(appName="Cura",
38 localFilename=mruFile,
39 style=wx.CONFIG_USE_LOCAL_FILE)
41 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)]
42 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
43 self.config.SetPath("/ModelMRU")
44 self.modelFileHistory.Load(self.config)
46 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)]
47 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
48 self.config.SetPath("/ProfileMRU")
49 self.profileFileHistory.Load(self.config)
51 self.menubar = wx.MenuBar()
52 self.fileMenu = wx.Menu()
53 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
54 self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowLoadModel(1), i)
55 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
56 self.Bind(wx.EVT_MENU, lambda e: self.scene.ShowPrintWindow(), i)
57 i = self.fileMenu.Append(-1, 'Save GCode...')
58 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), 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, 'Batch run...')
108 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
109 #self.normalModeOnlyItems.append(i)
110 if minecraftImport.hasMinecraft():
111 i = toolsMenu.Append(-1, 'Minecraft import...')
112 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
113 i = toolsMenu.Append(-1, 'Super-shaper...')
114 self.Bind(wx.EVT_MENU, self.OnSuperformula, i)
115 self.menubar.Append(toolsMenu, 'Tools')
117 expertMenu = wx.Menu()
118 i = expertMenu.Append(-1, 'Open expert settings...')
119 self.normalModeOnlyItems.append(i)
120 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
121 expertMenu.AppendSeparator()
122 if firmwareInstall.getDefaultFirmware() is not None:
123 i = expertMenu.Append(-1, 'Install default Marlin firmware')
124 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
125 i = expertMenu.Append(-1, 'Install custom firmware')
126 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
127 expertMenu.AppendSeparator()
128 i = expertMenu.Append(-1, 'Run first run wizard...')
129 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
130 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
131 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
132 self.menubar.Append(expertMenu, 'Expert')
135 i = helpMenu.Append(-1, 'Online documentation...')
136 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
137 i = helpMenu.Append(-1, 'Report a problem...')
138 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
139 i = helpMenu.Append(-1, 'Check for update...')
140 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
141 self.menubar.Append(helpMenu, 'Help')
142 self.SetMenuBar(self.menubar)
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 self.updateProfileToControls()
175 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
177 self.simpleSettingsPanel.Show(False)
178 self.normalSettingsPanel.Show(False)
180 # Set default window size & position
181 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
184 # Restore the window position, size & state from the preferences file
186 if profile.getPreference('window_maximized') == 'True':
189 posx = int(profile.getPreference('window_pos_x'))
190 posy = int(profile.getPreference('window_pos_y'))
191 width = int(profile.getPreference('window_width'))
192 height = int(profile.getPreference('window_height'))
193 if posx > 0 or posy > 0:
194 self.SetPosition((posx,posy))
195 if width > 0 and height > 0:
196 self.SetSize((width,height))
198 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
200 self.normalSashPos = 0
202 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
203 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
205 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
207 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
209 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
211 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
212 self.SetSize((800,600))
215 self.updateSliceMode()
217 def updateSliceMode(self):
218 isSimple = profile.getPreference('startMode') == 'Simple'
220 self.normalSettingsPanel.Show(not isSimple)
221 self.simpleSettingsPanel.Show(isSimple)
222 self.leftPane.Layout()
224 for i in self.normalModeOnlyItems:
225 i.Enable(not isSimple)
226 self.switchToQuickprintMenuItem.Enable(not isSimple)
227 self.switchToNormalMenuItem.Enable(isSimple)
229 # Set splitter sash position & size
231 # Save normal mode sash
232 self.normalSashPos = self.splitter.GetSashPosition()
234 # Change location of sash to width of quick mode pane
235 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
236 self.splitter.SetSashPosition(width, True)
239 self.splitter.SetSashSize(0)
241 self.splitter.SetSashPosition(self.normalSashPos, True)
243 self.splitter.SetSashSize(4)
244 self.scene.updateProfileToControls()
246 def OnPreferences(self, e):
247 prefDialog = preferencesDialog.preferencesDialog(self)
251 def OnDropFiles(self, files):
252 profile.setPluginConfig([])
253 self.updateProfileToControls()
254 self.scene.loadScene(files)
256 def OnModelMRU(self, e):
257 fileNum = e.GetId() - self.ID_MRU_MODEL1
258 path = self.modelFileHistory.GetHistoryFile(fileNum)
260 self.modelFileHistory.AddFileToHistory(path) # move up the list
261 self.config.SetPath("/ModelMRU")
262 self.modelFileHistory.Save(self.config)
266 self.scene.loadScene(filelist)
268 def addToModelMRU(self, file):
269 self.modelFileHistory.AddFileToHistory(file)
270 self.config.SetPath("/ModelMRU")
271 self.modelFileHistory.Save(self.config)
274 def OnProfileMRU(self, e):
275 fileNum = e.GetId() - self.ID_MRU_PROFILE1
276 path = self.profileFileHistory.GetHistoryFile(fileNum)
278 self.profileFileHistory.AddFileToHistory(path) # move up the list
279 self.config.SetPath("/ProfileMRU")
280 self.profileFileHistory.Save(self.config)
283 profile.loadProfile(path)
284 self.updateProfileToControls()
286 def addToProfileMRU(self, file):
287 self.profileFileHistory.AddFileToHistory(file)
288 self.config.SetPath("/ProfileMRU")
289 self.profileFileHistory.Save(self.config)
292 def updateProfileToControls(self):
293 self.scene.updateProfileToControls()
294 self.normalSettingsPanel.updateProfileToControls()
295 self.simpleSettingsPanel.updateProfileToControls()
297 def OnLoadProfile(self, e):
298 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)
299 dlg.SetWildcard("ini files (*.ini)|*.ini")
300 if dlg.ShowModal() == wx.ID_OK:
301 profileFile = dlg.GetPath()
302 profile.loadProfile(profileFile)
303 self.updateProfileToControls()
305 # Update the Profile MRU
306 self.addToProfileMRU(profileFile)
309 def OnLoadProfileFromGcode(self, e):
310 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)
311 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
312 if dlg.ShowModal() == wx.ID_OK:
313 gcodeFile = dlg.GetPath()
314 f = open(gcodeFile, 'r')
317 if line.startswith(';CURA_PROFILE_STRING:'):
318 profile.loadProfileFromString(line[line.find(':')+1:].strip())
321 self.updateProfileToControls()
323 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)
326 def OnSaveProfile(self, e):
327 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
328 dlg.SetWildcard("ini files (*.ini)|*.ini")
329 if dlg.ShowModal() == wx.ID_OK:
330 profileFile = dlg.GetPath()
331 profile.saveProfile(profileFile)
334 def OnResetProfile(self, e):
335 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)
336 result = dlg.ShowModal() == wx.ID_YES
339 profile.resetProfile()
340 self.updateProfileToControls()
342 def OnBatchRun(self, e):
343 br = batchRun.batchRunWindow(self)
347 def OnSimpleSwitch(self, e):
348 profile.putPreference('startMode', 'Simple')
349 self.updateSliceMode()
351 def OnNormalSwitch(self, e):
352 profile.putPreference('startMode', 'Normal')
353 self.updateSliceMode()
355 def OnDefaultMarlinFirmware(self, e):
356 firmwareInstall.InstallFirmware()
358 def OnCustomFirmware(self, e):
359 if profile.getPreference('machine_type') == 'ultimaker':
360 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)
361 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
362 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
363 if dlg.ShowModal() == wx.ID_OK:
364 filename = dlg.GetPath()
365 if not(os.path.exists(filename)):
367 #For some reason my Ubuntu 10.10 crashes here.
368 firmwareInstall.InstallFirmware(filename)
370 def OnFirstRunWizard(self, e):
371 configWizard.configWizard()
372 self.updateProfileToControls()
374 def OnBedLevelWizard(self, e):
375 configWizard.bedLevelWizard()
377 def OnExpertOpen(self, e):
378 ecw = expertConfig.expertConfigWindow()
381 self.scene.sceneUpdated()
383 def OnMinecraftImport(self, e):
384 mi = minecraftImport.minecraftImportWindow(self)
388 def OnSuperformula(self, e):
389 sf = superformula.superformulaWindow(self)
393 def OnCheckForUpdate(self, e):
394 newVersion = version.checkForNewerVersion()
395 if newVersion is not None:
396 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:
397 webbrowser.open(newVersion)
399 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
401 def OnClose(self, e):
402 profile.saveProfile(profile.getDefaultProfilePath())
404 # Save the window position, size & state from the preferences file
405 profile.putPreference('window_maximized', self.IsMaximized())
406 if not self.IsMaximized() and not self.IsIconized():
407 (posx, posy) = self.GetPosition()
408 profile.putPreference('window_pos_x', posx)
409 profile.putPreference('window_pos_y', posy)
410 (width, height) = self.GetSize()
411 profile.putPreference('window_width', width)
412 profile.putPreference('window_height', height)
414 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
415 isSimple = profile.getPreference('startMode') == 'Simple'
417 self.normalSashPos = self.splitter.GetSashPosition()
418 profile.putPreference('window_normal_sash', self.normalSashPos)
420 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
422 self.scene.OnPaint = lambda e : e
423 self.scene._slicer.cleanup()
429 class normalSettingsPanel(configBase.configPanelBase):
430 "Main user interface window"
431 def __init__(self, parent, callback = None):
432 super(normalSettingsPanel, self).__init__(parent, callback)
435 self.nb = wx.Notebook(self)
436 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
437 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
439 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
440 self._addSettingsToPanels('basic', left, right)
441 self.SizeLabelWidths(left, right)
443 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
444 self._addSettingsToPanels('advanced', left, right)
445 self.SizeLabelWidths(left, right)
448 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
449 if len(self.pluginPanel.pluginList) > 0 and False:
450 self.nb.AddPage(self.pluginPanel, "Plugins")
452 self.pluginPanel.Show(False)
455 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
456 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
458 self.Bind(wx.EVT_SIZE, self.OnSize)
460 self.nb.SetSize(self.GetSize())
461 self.UpdateSize(self.printPanel)
462 self.UpdateSize(self.advancedPanel)
464 def _addSettingsToPanels(self, category, left, right):
465 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
469 for title in profile.getSubCategoriesFor(category):
470 n += 1 + len(profile.getSettingsForCategory(category, title))
473 configBase.TitleRow(p, title)
474 for s in profile.getSettingsForCategory(category, title):
475 if s.checkConditions():
476 configBase.SettingRow(p, s.getName())
478 def SizeLabelWidths(self, left, right):
479 leftWidth = self.getLabelColumnWidth(left)
480 rightWidth = self.getLabelColumnWidth(right)
481 maxWidth = max(leftWidth, rightWidth)
482 self.setLabelColumnWidth(left, maxWidth)
483 self.setLabelColumnWidth(right, maxWidth)
486 # Make the size of the Notebook control the same size as this control
487 self.nb.SetSize(self.GetSize())
489 # Propegate the OnSize() event (just in case)
492 # Perform out resize magic
493 self.UpdateSize(self.printPanel)
494 self.UpdateSize(self.advancedPanel)
496 def UpdateSize(self, configPanel):
497 sizer = configPanel.GetSizer()
501 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
504 # if width(col1) > (best_width(col1) + best_width(col1)):
505 # switch to horizontal
508 col1 = configPanel.leftPanel
509 colSize1 = col1.GetSize()
510 colBestSize1 = col1.GetBestSize()
511 col2 = configPanel.rightPanel
512 colSize2 = col2.GetSize()
513 colBestSize2 = col2.GetBestSize()
515 orientation = sizer.GetOrientation()
517 if orientation == wx.HORIZONTAL:
518 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
520 sizer = wx.BoxSizer(wx.VERTICAL)
521 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
522 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
523 configPanel.SetSizer(sizer)
529 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
531 sizer = wx.BoxSizer(wx.HORIZONTAL)
532 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
533 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
534 configPanel.SetSizer(sizer)
540 def updateProfileToControls(self):
541 super(normalSettingsPanel, self).updateProfileToControls()
542 self.alterationPanel.updateProfileToControls()
543 self.pluginPanel.updateProfileToControls()