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.loadSupportedExtensions()))
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(), i)
55 i = self.fileMenu.Append(-1, 'Save model...\tCTRL+S')
56 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
58 self.fileMenu.AppendSeparator()
59 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
60 self.Bind(wx.EVT_MENU, lambda e: self.scene.showPrintWindow(), i)
61 i = self.fileMenu.Append(-1, 'Save GCode...')
62 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
64 self.fileMenu.AppendSeparator()
65 i = self.fileMenu.Append(-1, 'Open Profile...')
66 self.normalModeOnlyItems.append(i)
67 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
68 i = self.fileMenu.Append(-1, 'Save Profile...')
69 self.normalModeOnlyItems.append(i)
70 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
71 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
72 self.normalModeOnlyItems.append(i)
73 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
74 self.fileMenu.AppendSeparator()
75 i = self.fileMenu.Append(-1, 'Reset Profile to default')
76 self.normalModeOnlyItems.append(i)
77 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
79 self.fileMenu.AppendSeparator()
80 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
81 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
82 self.fileMenu.AppendSeparator()
85 modelHistoryMenu = wx.Menu()
86 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
87 self.modelFileHistory.UseMenu(modelHistoryMenu)
88 self.modelFileHistory.AddFilesToMenu()
89 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
92 profileHistoryMenu = wx.Menu()
93 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
94 self.profileFileHistory.UseMenu(profileHistoryMenu)
95 self.profileFileHistory.AddFilesToMenu()
96 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
98 self.fileMenu.AppendSeparator()
99 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
100 self.Bind(wx.EVT_MENU, self.OnQuit, i)
101 self.menubar.Append(self.fileMenu, '&File')
103 toolsMenu = wx.Menu()
104 i = toolsMenu.Append(-1, 'Switch to quickprint...')
105 self.switchToQuickprintMenuItem = i
106 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
107 i = toolsMenu.Append(-1, 'Switch to full settings...')
108 self.switchToNormalMenuItem = i
109 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
110 toolsMenu.AppendSeparator()
111 #i = toolsMenu.Append(-1, 'Batch run...')
112 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
113 #self.normalModeOnlyItems.append(i)
114 if minecraftImport.hasMinecraft():
115 i = toolsMenu.Append(-1, 'Minecraft import...')
116 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
117 i = toolsMenu.Append(-1, 'Super-shaper...')
118 self.Bind(wx.EVT_MENU, self.OnSuperformula, i)
119 self.menubar.Append(toolsMenu, 'Tools')
121 expertMenu = wx.Menu()
122 i = expertMenu.Append(-1, 'Open expert settings...')
123 self.normalModeOnlyItems.append(i)
124 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
125 expertMenu.AppendSeparator()
126 if firmwareInstall.getDefaultFirmware() is not None:
127 i = expertMenu.Append(-1, 'Install default Marlin firmware')
128 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
129 i = expertMenu.Append(-1, 'Install custom firmware')
130 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
131 expertMenu.AppendSeparator()
132 i = expertMenu.Append(-1, 'Run first run wizard...')
133 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
134 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
135 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
136 self.menubar.Append(expertMenu, 'Expert')
139 i = helpMenu.Append(-1, 'Online documentation...')
140 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
141 i = helpMenu.Append(-1, 'Report a problem...')
142 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
143 i = helpMenu.Append(-1, 'Check for update...')
144 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
145 self.menubar.Append(helpMenu, 'Help')
146 self.SetMenuBar(self.menubar)
148 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
149 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
150 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
151 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
154 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
155 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
157 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
158 self.leftSizer.Add(self.simpleSettingsPanel)
159 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
160 self.leftPane.SetSizer(self.leftSizer)
163 self.scene = sceneView.SceneView(self.rightPane)
165 #Main sizer, to position the preview window, buttons and tab control
166 sizer = wx.BoxSizer()
167 self.rightPane.SetSizer(sizer)
168 sizer.Add(self.scene, 1, flag=wx.EXPAND)
171 sizer = wx.BoxSizer(wx.VERTICAL)
173 sizer.Add(self.splitter, 1, wx.EXPAND)
177 self.updateProfileToControls()
179 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
181 self.simpleSettingsPanel.Show(False)
182 self.normalSettingsPanel.Show(False)
184 # Set default window size & position
185 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
188 # Restore the window position, size & state from the preferences file
190 if profile.getPreference('window_maximized') == 'True':
193 posx = int(profile.getPreference('window_pos_x'))
194 posy = int(profile.getPreference('window_pos_y'))
195 width = int(profile.getPreference('window_width'))
196 height = int(profile.getPreference('window_height'))
197 if posx > 0 or posy > 0:
198 self.SetPosition((posx,posy))
199 if width > 0 and height > 0:
200 self.SetSize((width,height))
202 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
204 self.normalSashPos = 0
206 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
207 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
209 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
211 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
213 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
215 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
216 self.SetSize((800,600))
219 self.updateSliceMode()
221 def updateSliceMode(self):
222 isSimple = profile.getPreference('startMode') == 'Simple'
224 self.normalSettingsPanel.Show(not isSimple)
225 self.simpleSettingsPanel.Show(isSimple)
226 self.leftPane.Layout()
228 for i in self.normalModeOnlyItems:
229 i.Enable(not isSimple)
230 self.switchToQuickprintMenuItem.Enable(not isSimple)
231 self.switchToNormalMenuItem.Enable(isSimple)
233 # Set splitter sash position & size
235 # Save normal mode sash
236 self.normalSashPos = self.splitter.GetSashPosition()
238 # Change location of sash to width of quick mode pane
239 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
240 self.splitter.SetSashPosition(width, True)
243 self.splitter.SetSashSize(0)
245 self.splitter.SetSashPosition(self.normalSashPos, True)
247 self.splitter.SetSashSize(4)
248 self.scene.updateProfileToControls()
250 def OnPreferences(self, e):
251 prefDialog = preferencesDialog.preferencesDialog(self)
255 def OnDropFiles(self, files):
256 profile.setPluginConfig([])
257 self.updateProfileToControls()
258 self.scene.loadScene(files)
260 def OnModelMRU(self, e):
261 fileNum = e.GetId() - self.ID_MRU_MODEL1
262 path = self.modelFileHistory.GetHistoryFile(fileNum)
264 self.modelFileHistory.AddFileToHistory(path) # move up the list
265 self.config.SetPath("/ModelMRU")
266 self.modelFileHistory.Save(self.config)
270 self.scene.loadScene(filelist)
272 def addToModelMRU(self, file):
273 self.modelFileHistory.AddFileToHistory(file)
274 self.config.SetPath("/ModelMRU")
275 self.modelFileHistory.Save(self.config)
278 def OnProfileMRU(self, e):
279 fileNum = e.GetId() - self.ID_MRU_PROFILE1
280 path = self.profileFileHistory.GetHistoryFile(fileNum)
282 self.profileFileHistory.AddFileToHistory(path) # move up the list
283 self.config.SetPath("/ProfileMRU")
284 self.profileFileHistory.Save(self.config)
287 profile.loadProfile(path)
288 self.updateProfileToControls()
290 def addToProfileMRU(self, file):
291 self.profileFileHistory.AddFileToHistory(file)
292 self.config.SetPath("/ProfileMRU")
293 self.profileFileHistory.Save(self.config)
296 def updateProfileToControls(self):
297 self.scene.updateProfileToControls()
298 self.normalSettingsPanel.updateProfileToControls()
299 self.simpleSettingsPanel.updateProfileToControls()
301 def OnLoadProfile(self, e):
302 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)
303 dlg.SetWildcard("ini files (*.ini)|*.ini")
304 if dlg.ShowModal() == wx.ID_OK:
305 profileFile = dlg.GetPath()
306 profile.loadProfile(profileFile)
307 self.updateProfileToControls()
309 # Update the Profile MRU
310 self.addToProfileMRU(profileFile)
313 def OnLoadProfileFromGcode(self, e):
314 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)
315 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
316 if dlg.ShowModal() == wx.ID_OK:
317 gcodeFile = dlg.GetPath()
318 f = open(gcodeFile, 'r')
321 if line.startswith(';CURA_PROFILE_STRING:'):
322 profile.loadProfileFromString(line[line.find(':')+1:].strip())
325 self.updateProfileToControls()
327 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)
330 def OnSaveProfile(self, e):
331 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
332 dlg.SetWildcard("ini files (*.ini)|*.ini")
333 if dlg.ShowModal() == wx.ID_OK:
334 profileFile = dlg.GetPath()
335 profile.saveProfile(profileFile)
338 def OnResetProfile(self, e):
339 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)
340 result = dlg.ShowModal() == wx.ID_YES
343 profile.resetProfile()
344 self.updateProfileToControls()
346 def OnBatchRun(self, e):
347 br = batchRun.batchRunWindow(self)
351 def OnSimpleSwitch(self, e):
352 profile.putPreference('startMode', 'Simple')
353 self.updateSliceMode()
355 def OnNormalSwitch(self, e):
356 profile.putPreference('startMode', 'Normal')
357 self.updateSliceMode()
359 def OnDefaultMarlinFirmware(self, e):
360 firmwareInstall.InstallFirmware()
362 def OnCustomFirmware(self, e):
363 if profile.getPreference('machine_type') == 'ultimaker':
364 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)
365 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
366 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
367 if dlg.ShowModal() == wx.ID_OK:
368 filename = dlg.GetPath()
369 if not(os.path.exists(filename)):
371 #For some reason my Ubuntu 10.10 crashes here.
372 firmwareInstall.InstallFirmware(filename)
374 def OnFirstRunWizard(self, e):
375 configWizard.configWizard()
376 self.updateProfileToControls()
378 def OnBedLevelWizard(self, e):
379 configWizard.bedLevelWizard()
381 def OnExpertOpen(self, e):
382 ecw = expertConfig.expertConfigWindow()
385 self.scene.sceneUpdated()
387 def OnMinecraftImport(self, e):
388 mi = minecraftImport.minecraftImportWindow(self)
392 def OnSuperformula(self, e):
393 sf = superformula.superformulaWindow(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, callback)
453 if len(self.pluginPanel.pluginList) > 0:
454 self.nb.AddPage(self.pluginPanel, "Plugins")
456 self.pluginPanel.Show(False)
459 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
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()