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 if self.extruderCount > 1:
137 i = expertMenu.Append(-1, 'Run head offset wizard...')
138 self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, i)
139 self.menubar.Append(expertMenu, 'Expert')
142 i = helpMenu.Append(-1, 'Online documentation...')
143 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
144 i = helpMenu.Append(-1, 'Report a problem...')
145 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
146 i = helpMenu.Append(-1, 'Check for update...')
147 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
148 self.menubar.Append(helpMenu, 'Help')
149 self.SetMenuBar(self.menubar)
151 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
152 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
153 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
154 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
157 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
158 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
160 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
161 self.leftSizer.Add(self.simpleSettingsPanel)
162 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
163 self.leftPane.SetSizer(self.leftSizer)
166 self.scene = sceneView.SceneView(self.rightPane)
168 #Main sizer, to position the preview window, buttons and tab control
169 sizer = wx.BoxSizer()
170 self.rightPane.SetSizer(sizer)
171 sizer.Add(self.scene, 1, flag=wx.EXPAND)
174 sizer = wx.BoxSizer(wx.VERTICAL)
176 sizer.Add(self.splitter, 1, wx.EXPAND)
180 self.updateProfileToControls()
182 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
184 self.simpleSettingsPanel.Show(False)
185 self.normalSettingsPanel.Show(False)
187 # Set default window size & position
188 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
191 # Restore the window position, size & state from the preferences file
193 if profile.getPreference('window_maximized') == 'True':
196 posx = int(profile.getPreference('window_pos_x'))
197 posy = int(profile.getPreference('window_pos_y'))
198 width = int(profile.getPreference('window_width'))
199 height = int(profile.getPreference('window_height'))
200 if posx > 0 or posy > 0:
201 self.SetPosition((posx,posy))
202 if width > 0 and height > 0:
203 self.SetSize((width,height))
205 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
207 self.normalSashPos = 0
209 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
210 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
212 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
214 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
216 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
218 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
219 self.SetSize((800,600))
222 self.updateSliceMode()
224 def updateSliceMode(self):
225 isSimple = profile.getPreference('startMode') == 'Simple'
227 self.normalSettingsPanel.Show(not isSimple)
228 self.simpleSettingsPanel.Show(isSimple)
229 self.leftPane.Layout()
231 for i in self.normalModeOnlyItems:
232 i.Enable(not isSimple)
233 self.switchToQuickprintMenuItem.Enable(not isSimple)
234 self.switchToNormalMenuItem.Enable(isSimple)
236 # Set splitter sash position & size
238 # Save normal mode sash
239 self.normalSashPos = self.splitter.GetSashPosition()
241 # Change location of sash to width of quick mode pane
242 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
243 self.splitter.SetSashPosition(width, True)
246 self.splitter.SetSashSize(0)
248 self.splitter.SetSashPosition(self.normalSashPos, True)
250 self.splitter.SetSashSize(4)
251 self.scene.updateProfileToControls()
253 def OnPreferences(self, e):
254 prefDialog = preferencesDialog.preferencesDialog(self)
258 def OnDropFiles(self, files):
259 profile.setPluginConfig([])
260 self.updateProfileToControls()
261 self.scene.loadScene(files)
263 def OnModelMRU(self, e):
264 fileNum = e.GetId() - self.ID_MRU_MODEL1
265 path = self.modelFileHistory.GetHistoryFile(fileNum)
267 self.modelFileHistory.AddFileToHistory(path) # move up the list
268 self.config.SetPath("/ModelMRU")
269 self.modelFileHistory.Save(self.config)
273 self.scene.loadScene(filelist)
275 def addToModelMRU(self, file):
276 self.modelFileHistory.AddFileToHistory(file)
277 self.config.SetPath("/ModelMRU")
278 self.modelFileHistory.Save(self.config)
281 def OnProfileMRU(self, e):
282 fileNum = e.GetId() - self.ID_MRU_PROFILE1
283 path = self.profileFileHistory.GetHistoryFile(fileNum)
285 self.profileFileHistory.AddFileToHistory(path) # move up the list
286 self.config.SetPath("/ProfileMRU")
287 self.profileFileHistory.Save(self.config)
290 profile.loadProfile(path)
291 self.updateProfileToControls()
293 def addToProfileMRU(self, file):
294 self.profileFileHistory.AddFileToHistory(file)
295 self.config.SetPath("/ProfileMRU")
296 self.profileFileHistory.Save(self.config)
299 def updateProfileToControls(self):
300 self.scene.updateProfileToControls()
301 self.normalSettingsPanel.updateProfileToControls()
302 self.simpleSettingsPanel.updateProfileToControls()
304 def OnLoadProfile(self, e):
305 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)
306 dlg.SetWildcard("ini files (*.ini)|*.ini")
307 if dlg.ShowModal() == wx.ID_OK:
308 profileFile = dlg.GetPath()
309 profile.loadProfile(profileFile)
310 self.updateProfileToControls()
312 # Update the Profile MRU
313 self.addToProfileMRU(profileFile)
316 def OnLoadProfileFromGcode(self, e):
317 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)
318 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
319 if dlg.ShowModal() == wx.ID_OK:
320 gcodeFile = dlg.GetPath()
321 f = open(gcodeFile, 'r')
324 if line.startswith(';CURA_PROFILE_STRING:'):
325 profile.loadProfileFromString(line[line.find(':')+1:].strip())
328 self.updateProfileToControls()
330 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)
333 def OnSaveProfile(self, e):
334 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
335 dlg.SetWildcard("ini files (*.ini)|*.ini")
336 if dlg.ShowModal() == wx.ID_OK:
337 profileFile = dlg.GetPath()
338 profile.saveProfile(profileFile)
341 def OnResetProfile(self, e):
342 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)
343 result = dlg.ShowModal() == wx.ID_YES
346 profile.resetProfile()
347 self.updateProfileToControls()
349 def OnBatchRun(self, e):
350 br = batchRun.batchRunWindow(self)
354 def OnSimpleSwitch(self, e):
355 profile.putPreference('startMode', 'Simple')
356 self.updateSliceMode()
358 def OnNormalSwitch(self, e):
359 profile.putPreference('startMode', 'Normal')
360 self.updateSliceMode()
362 def OnDefaultMarlinFirmware(self, e):
363 firmwareInstall.InstallFirmware()
365 def OnCustomFirmware(self, e):
366 if profile.getPreference('machine_type') == 'ultimaker':
367 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)
368 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
369 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
370 if dlg.ShowModal() == wx.ID_OK:
371 filename = dlg.GetPath()
372 if not(os.path.exists(filename)):
374 #For some reason my Ubuntu 10.10 crashes here.
375 firmwareInstall.InstallFirmware(filename)
377 def OnFirstRunWizard(self, e):
378 configWizard.configWizard()
379 self.updateProfileToControls()
381 def OnBedLevelWizard(self, e):
382 configWizard.bedLevelWizard()
384 def OnHeadOffsetWizard(self, e):
385 configWizard.headOffsetWizard()
387 def OnExpertOpen(self, e):
388 ecw = expertConfig.expertConfigWindow()
391 self.scene.sceneUpdated()
393 def OnMinecraftImport(self, e):
394 mi = minecraftImport.minecraftImportWindow(self)
398 def OnSuperformula(self, e):
399 sf = superformula.superformulaWindow(self)
403 def OnCheckForUpdate(self, e):
404 newVersion = version.checkForNewerVersion()
405 if newVersion is not None:
406 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:
407 webbrowser.open(newVersion)
409 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
411 def OnClose(self, e):
412 profile.saveProfile(profile.getDefaultProfilePath())
414 # Save the window position, size & state from the preferences file
415 profile.putPreference('window_maximized', self.IsMaximized())
416 if not self.IsMaximized() and not self.IsIconized():
417 (posx, posy) = self.GetPosition()
418 profile.putPreference('window_pos_x', posx)
419 profile.putPreference('window_pos_y', posy)
420 (width, height) = self.GetSize()
421 profile.putPreference('window_width', width)
422 profile.putPreference('window_height', height)
424 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
425 isSimple = profile.getPreference('startMode') == 'Simple'
427 self.normalSashPos = self.splitter.GetSashPosition()
428 profile.putPreference('window_normal_sash', self.normalSashPos)
430 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
432 self.scene.OnPaint = lambda e : e
433 self.scene._slicer.cleanup()
439 class normalSettingsPanel(configBase.configPanelBase):
440 "Main user interface window"
441 def __init__(self, parent, callback = None):
442 super(normalSettingsPanel, self).__init__(parent, callback)
445 self.nb = wx.Notebook(self)
446 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
447 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
449 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
450 self._addSettingsToPanels('basic', left, right)
451 self.SizeLabelWidths(left, right)
453 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
454 self._addSettingsToPanels('advanced', left, right)
455 self.SizeLabelWidths(left, right)
458 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
459 if len(self.pluginPanel.pluginList) > 0:
460 self.nb.AddPage(self.pluginPanel, "Plugins")
462 self.pluginPanel.Show(False)
465 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
466 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
468 self.Bind(wx.EVT_SIZE, self.OnSize)
470 self.nb.SetSize(self.GetSize())
471 self.UpdateSize(self.printPanel)
472 self.UpdateSize(self.advancedPanel)
474 def _addSettingsToPanels(self, category, left, right):
475 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
479 for title in profile.getSubCategoriesFor(category):
480 n += 1 + len(profile.getSettingsForCategory(category, title))
483 configBase.TitleRow(p, title)
484 for s in profile.getSettingsForCategory(category, title):
485 if s.checkConditions():
486 configBase.SettingRow(p, s.getName())
488 def SizeLabelWidths(self, left, right):
489 leftWidth = self.getLabelColumnWidth(left)
490 rightWidth = self.getLabelColumnWidth(right)
491 maxWidth = max(leftWidth, rightWidth)
492 self.setLabelColumnWidth(left, maxWidth)
493 self.setLabelColumnWidth(right, maxWidth)
496 # Make the size of the Notebook control the same size as this control
497 self.nb.SetSize(self.GetSize())
499 # Propegate the OnSize() event (just in case)
502 # Perform out resize magic
503 self.UpdateSize(self.printPanel)
504 self.UpdateSize(self.advancedPanel)
506 def UpdateSize(self, configPanel):
507 sizer = configPanel.GetSizer()
511 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
514 # if width(col1) > (best_width(col1) + best_width(col1)):
515 # switch to horizontal
518 col1 = configPanel.leftPanel
519 colSize1 = col1.GetSize()
520 colBestSize1 = col1.GetBestSize()
521 col2 = configPanel.rightPanel
522 colSize2 = col2.GetSize()
523 colBestSize2 = col2.GetBestSize()
525 orientation = sizer.GetOrientation()
527 if orientation == wx.HORIZONTAL:
528 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
530 sizer = wx.BoxSizer(wx.VERTICAL)
531 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
532 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
533 configPanel.SetSizer(sizer)
539 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
541 sizer = wx.BoxSizer(wx.HORIZONTAL)
542 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
543 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
544 configPanel.SetSizer(sizer)
550 def updateProfileToControls(self):
551 super(normalSettingsPanel, self).updateProfileToControls()
552 self.alterationPanel.updateProfileToControls()
553 self.pluginPanel.updateProfileToControls()