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'))
213 self.normalSashPos = 0
215 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
216 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
218 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
220 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
222 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
224 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
225 self.SetSize((800,600))
228 self.updateSliceMode()
232 def updateSliceMode(self):
233 isSimple = profile.getPreference('startMode') == 'Simple'
235 self.normalSettingsPanel.Show(not isSimple)
236 self.simpleSettingsPanel.Show(isSimple)
237 self.leftPane.Layout()
239 for i in self.normalModeOnlyItems:
240 i.Enable(not isSimple)
241 self.switchToQuickprintMenuItem.Enable(not isSimple)
242 self.switchToNormalMenuItem.Enable(isSimple)
244 # Set splitter sash position & size
246 # Save normal mode sash
247 self.normalSashPos = self.splitter.GetSashPosition()
249 # Change location of sash to width of quick mode pane
250 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
251 self.splitter.SetSashPosition(width, True)
254 self.splitter.SetSashSize(0)
256 self.splitter.SetSashPosition(self.normalSashPos, True)
258 self.splitter.SetSashSize(4)
259 self.scene.updateProfileToControls()
261 def OnPreferences(self, e):
262 prefDialog = preferencesDialog.preferencesDialog(self)
264 prefDialog.Show(True)
266 def OnDropFiles(self, files):
267 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
268 profile.setPluginConfig([])
269 self.updateProfileToControls()
270 self.scene.loadScene(files)
272 def OnPrint(self, e):
273 if len(self.filelist) < 1:
274 wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
276 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
277 wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
279 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
281 def OnModelMRU(self, e):
282 fileNum = e.GetId() - self.ID_MRU_MODEL1
283 path = self.modelFileHistory.GetHistoryFile(fileNum)
285 self.modelFileHistory.AddFileToHistory(path) # move up the list
286 self.config.SetPath("/ModelMRU")
287 self.modelFileHistory.Save(self.config)
291 self.scene.loadScene(filelist)
293 def addToModelMRU(self, file):
294 self.modelFileHistory.AddFileToHistory(file)
295 self.config.SetPath("/ModelMRU")
296 self.modelFileHistory.Save(self.config)
299 def OnProfileMRU(self, e):
300 fileNum = e.GetId() - self.ID_MRU_PROFILE1
301 path = self.profileFileHistory.GetHistoryFile(fileNum)
303 self.profileFileHistory.AddFileToHistory(path) # move up the list
304 self.config.SetPath("/ProfileMRU")
305 self.profileFileHistory.Save(self.config)
308 profile.loadProfile(path)
309 self.updateProfileToControls()
311 def addToProfileMRU(self, file):
312 self.profileFileHistory.AddFileToHistory(file)
313 self.config.SetPath("/ProfileMRU")
314 self.profileFileHistory.Save(self.config)
317 def updateProfileToControls(self):
318 self.scene.updateProfileToControls()
319 self.normalSettingsPanel.updateProfileToControls()
320 self.simpleSettingsPanel.updateProfileToControls()
322 def OnLoadProfile(self, e):
323 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)
324 dlg.SetWildcard("ini files (*.ini)|*.ini")
325 if dlg.ShowModal() == wx.ID_OK:
326 profileFile = dlg.GetPath()
327 profile.loadProfile(profileFile)
328 self.updateProfileToControls()
330 # Update the Profile MRU
331 self.addToProfileMRU(profileFile)
334 def OnLoadProfileFromGcode(self, e):
335 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)
336 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
337 if dlg.ShowModal() == wx.ID_OK:
338 gcodeFile = dlg.GetPath()
339 f = open(gcodeFile, 'r')
342 if line.startswith(';CURA_PROFILE_STRING:'):
343 profile.loadProfileFromString(line[line.find(':')+1:].strip())
346 self.updateProfileToControls()
348 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)
351 def OnSaveProfile(self, e):
352 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
353 dlg.SetWildcard("ini files (*.ini)|*.ini")
354 if dlg.ShowModal() == wx.ID_OK:
355 profileFile = dlg.GetPath()
356 profile.saveProfile(profileFile)
359 def OnResetProfile(self, e):
360 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)
361 result = dlg.ShowModal() == wx.ID_YES
364 profile.resetProfile()
365 self.updateProfileToControls()
367 def OnBatchRun(self, e):
368 br = batchRun.batchRunWindow(self)
372 def OnSimpleSwitch(self, e):
373 profile.putPreference('startMode', 'Simple')
374 self.updateSliceMode()
376 def OnNormalSwitch(self, e):
377 profile.putPreference('startMode', 'Normal')
378 self.updateSliceMode()
380 def OnDefaultMarlinFirmware(self, e):
381 firmwareInstall.InstallFirmware()
383 def OnCustomFirmware(self, e):
384 if profile.getPreference('machine_type') == 'ultimaker':
385 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)
386 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
387 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
388 if dlg.ShowModal() == wx.ID_OK:
389 filename = dlg.GetPath()
390 if not(os.path.exists(filename)):
392 #For some reason my Ubuntu 10.10 crashes here.
393 firmwareInstall.InstallFirmware(filename)
395 def OnFirstRunWizard(self, e):
396 configWizard.configWizard()
397 self.updateProfileToControls()
399 def OnBedLevelWizard(self, e):
400 configWizard.bedLevelWizard()
402 def OnExpertOpen(self, e):
403 ecw = expertConfig.expertConfigWindow()
407 def OnProjectPlanner(self, e):
408 pp = projectPlanner.projectPlanner()
412 def OnMinecraftImport(self, e):
413 mi = minecraftImport.minecraftImportWindow(self)
417 def OnCheckForUpdate(self, e):
418 newVersion = version.checkForNewerVersion()
419 if newVersion is not None:
420 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:
421 webbrowser.open(newVersion)
423 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
425 def OnClose(self, e):
426 profile.saveProfile(profile.getDefaultProfilePath())
428 # Save the window position, size & state from the preferences file
429 profile.putPreference('window_maximized', self.IsMaximized())
430 if not self.IsMaximized() and not self.IsIconized():
431 (posx, posy) = self.GetPosition()
432 profile.putPreference('window_pos_x', posx)
433 profile.putPreference('window_pos_y', posy)
434 (width, height) = self.GetSize()
435 profile.putPreference('window_width', width)
436 profile.putPreference('window_height', height)
438 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
439 isSimple = profile.getPreference('startMode') == 'Simple'
441 self.normalSashPos = self.splitter.GetSashPosition()
442 profile.putPreference('window_normal_sash', self.normalSashPos)
444 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
446 self.scene.OnPaint = lambda e : e
452 class normalSettingsPanel(configBase.configPanelBase):
453 "Main user interface window"
454 def __init__(self, parent, callback = None):
455 super(normalSettingsPanel, self).__init__(parent, callback)
458 self.nb = wx.Notebook(self)
459 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
460 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
462 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
463 self._addSettingsToPanels('basic', left, right)
464 self.SizeLabelWidths(left, right)
466 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
467 self._addSettingsToPanels('advanced', left, right)
468 self.SizeLabelWidths(left, right)
471 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
472 if len(self.pluginPanel.pluginList) > 0:
473 self.nb.AddPage(self.pluginPanel, "Plugins")
475 self.pluginPanel.Show(False)
478 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
479 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
481 self.Bind(wx.EVT_SIZE, self.OnSize)
483 self.nb.SetSize(self.GetSize())
484 self.UpdateSize(self.printPanel)
485 self.UpdateSize(self.advancedPanel)
487 def _addSettingsToPanels(self, category, left, right):
488 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
492 for title in profile.getSubCategoriesFor(category):
493 n += 1 + len(profile.getSettingsForCategory(category, title))
496 configBase.TitleRow(p, title)
497 for s in profile.getSettingsForCategory(category, title):
498 if s.checkConditions():
499 configBase.SettingRow(p, s.getName())
501 def SizeLabelWidths(self, left, right):
502 leftWidth = self.getLabelColumnWidth(left)
503 rightWidth = self.getLabelColumnWidth(right)
504 maxWidth = max(leftWidth, rightWidth)
505 self.setLabelColumnWidth(left, maxWidth)
506 self.setLabelColumnWidth(right, maxWidth)
509 # Make the size of the Notebook control the same size as this control
510 self.nb.SetSize(self.GetSize())
512 # Propegate the OnSize() event (just in case)
515 # Perform out resize magic
516 self.UpdateSize(self.printPanel)
517 self.UpdateSize(self.advancedPanel)
519 def UpdateSize(self, configPanel):
520 sizer = configPanel.GetSizer()
524 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
527 # if width(col1) > (best_width(col1) + best_width(col1)):
528 # switch to horizontal
531 col1 = configPanel.leftPanel
532 colSize1 = col1.GetSize()
533 colBestSize1 = col1.GetBestSize()
534 col2 = configPanel.rightPanel
535 colSize2 = col2.GetSize()
536 colBestSize2 = col2.GetBestSize()
538 orientation = sizer.GetOrientation()
540 if orientation == wx.HORIZONTAL:
541 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
543 sizer = wx.BoxSizer(wx.VERTICAL)
544 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
545 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
546 configPanel.SetSizer(sizer)
552 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
554 sizer = wx.BoxSizer(wx.HORIZONTAL)
555 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
556 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
557 configPanel.SetSizer(sizer)
563 def updateProfileToControls(self):
564 super(normalSettingsPanel, self).updateProfileToControls()
565 self.alterationPanel.updateProfileToControls()
566 self.pluginPanel.updateProfileToControls()