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)
58 self.fileMenu.AppendSeparator()
59 i = self.fileMenu.Append(-1, 'Open Profile...')
60 self.normalModeOnlyItems.append(i)
61 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
62 i = self.fileMenu.Append(-1, 'Save Profile...')
63 self.normalModeOnlyItems.append(i)
64 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
65 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
66 self.normalModeOnlyItems.append(i)
67 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
68 self.fileMenu.AppendSeparator()
69 i = self.fileMenu.Append(-1, 'Reset Profile to default')
70 self.normalModeOnlyItems.append(i)
71 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
73 self.fileMenu.AppendSeparator()
74 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
75 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
76 self.fileMenu.AppendSeparator()
79 modelHistoryMenu = wx.Menu()
80 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
81 self.modelFileHistory.UseMenu(modelHistoryMenu)
82 self.modelFileHistory.AddFilesToMenu()
83 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
86 profileHistoryMenu = wx.Menu()
87 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
88 self.profileFileHistory.UseMenu(profileHistoryMenu)
89 self.profileFileHistory.AddFilesToMenu()
90 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
92 self.fileMenu.AppendSeparator()
93 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
94 self.Bind(wx.EVT_MENU, self.OnQuit, i)
95 self.menubar.Append(self.fileMenu, '&File')
98 i = toolsMenu.Append(-1, 'Switch to quickprint...')
99 self.switchToQuickprintMenuItem = i
100 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
101 i = toolsMenu.Append(-1, 'Switch to full settings...')
102 self.switchToNormalMenuItem = i
103 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
104 toolsMenu.AppendSeparator()
105 #i = toolsMenu.Append(-1, 'Batch run...')
106 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
107 #self.normalModeOnlyItems.append(i)
108 if minecraftImport.hasMinecraft():
109 i = toolsMenu.Append(-1, 'Minecraft import...')
110 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
111 i = toolsMenu.Append(-1, 'Super-shaper...')
112 self.Bind(wx.EVT_MENU, self.OnSuperformula, i)
113 self.menubar.Append(toolsMenu, 'Tools')
115 expertMenu = wx.Menu()
116 i = expertMenu.Append(-1, 'Open expert settings...')
117 self.normalModeOnlyItems.append(i)
118 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
119 expertMenu.AppendSeparator()
120 if firmwareInstall.getDefaultFirmware() is not None:
121 i = expertMenu.Append(-1, 'Install default Marlin firmware')
122 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
123 i = expertMenu.Append(-1, 'Install custom firmware')
124 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
125 expertMenu.AppendSeparator()
126 i = expertMenu.Append(-1, 'Run first run wizard...')
127 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
128 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
129 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
130 self.menubar.Append(expertMenu, 'Expert')
133 i = helpMenu.Append(-1, 'Online documentation...')
134 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
135 i = helpMenu.Append(-1, 'Report a problem...')
136 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
137 i = helpMenu.Append(-1, 'Check for update...')
138 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
139 self.menubar.Append(helpMenu, 'Help')
140 self.SetMenuBar(self.menubar)
142 if profile.getPreference('lastFile') != '':
143 self.filelist = profile.getPreference('lastFile').split(';')
147 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
148 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
149 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
150 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
153 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
154 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
156 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
157 self.leftSizer.Add(self.simpleSettingsPanel)
158 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
159 self.leftPane.SetSizer(self.leftSizer)
162 self.scene = sceneView.SceneView(self.rightPane)
164 #Main sizer, to position the preview window, buttons and tab control
165 sizer = wx.BoxSizer()
166 self.rightPane.SetSizer(sizer)
167 sizer.Add(self.scene, 1, flag=wx.EXPAND)
170 sizer = wx.BoxSizer(wx.VERTICAL)
172 sizer.Add(self.splitter, 1, wx.EXPAND)
176 if len(self.filelist) > 0:
177 self.scene.loadScene(self.filelist)
179 # Update the Model MRU
180 for idx in xrange(0, len(self.filelist)):
181 self.addToModelMRU(self.filelist[idx])
183 self.updateProfileToControls()
185 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
187 self.simpleSettingsPanel.Show(False)
188 self.normalSettingsPanel.Show(False)
190 # Set default window size & position
191 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
194 # Restore the window position, size & state from the preferences file
196 if profile.getPreference('window_maximized') == 'True':
199 posx = int(profile.getPreference('window_pos_x'))
200 posy = int(profile.getPreference('window_pos_y'))
201 width = int(profile.getPreference('window_width'))
202 height = int(profile.getPreference('window_height'))
203 if posx > 0 or posy > 0:
204 self.SetPosition((posx,posy))
205 if width > 0 and height > 0:
206 self.SetSize((width,height))
208 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
210 self.normalSashPos = 0
212 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
213 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
215 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
217 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
219 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
221 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
222 self.SetSize((800,600))
225 self.updateSliceMode()
229 def updateSliceMode(self):
230 isSimple = profile.getPreference('startMode') == 'Simple'
232 self.normalSettingsPanel.Show(not isSimple)
233 self.simpleSettingsPanel.Show(isSimple)
234 self.leftPane.Layout()
236 for i in self.normalModeOnlyItems:
237 i.Enable(not isSimple)
238 self.switchToQuickprintMenuItem.Enable(not isSimple)
239 self.switchToNormalMenuItem.Enable(isSimple)
241 # Set splitter sash position & size
243 # Save normal mode sash
244 self.normalSashPos = self.splitter.GetSashPosition()
246 # Change location of sash to width of quick mode pane
247 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
248 self.splitter.SetSashPosition(width, True)
251 self.splitter.SetSashSize(0)
253 self.splitter.SetSashPosition(self.normalSashPos, True)
255 self.splitter.SetSashSize(4)
256 self.scene.updateProfileToControls()
258 def OnPreferences(self, e):
259 prefDialog = preferencesDialog.preferencesDialog(self)
263 def OnDropFiles(self, files):
264 profile.setPluginConfig([])
265 self.updateProfileToControls()
266 self.scene.loadScene(files)
268 def OnModelMRU(self, e):
269 fileNum = e.GetId() - self.ID_MRU_MODEL1
270 path = self.modelFileHistory.GetHistoryFile(fileNum)
272 self.modelFileHistory.AddFileToHistory(path) # move up the list
273 self.config.SetPath("/ModelMRU")
274 self.modelFileHistory.Save(self.config)
278 self.scene.loadScene(filelist)
280 def addToModelMRU(self, file):
281 self.modelFileHistory.AddFileToHistory(file)
282 self.config.SetPath("/ModelMRU")
283 self.modelFileHistory.Save(self.config)
286 def OnProfileMRU(self, e):
287 fileNum = e.GetId() - self.ID_MRU_PROFILE1
288 path = self.profileFileHistory.GetHistoryFile(fileNum)
290 self.profileFileHistory.AddFileToHistory(path) # move up the list
291 self.config.SetPath("/ProfileMRU")
292 self.profileFileHistory.Save(self.config)
295 profile.loadProfile(path)
296 self.updateProfileToControls()
298 def addToProfileMRU(self, file):
299 self.profileFileHistory.AddFileToHistory(file)
300 self.config.SetPath("/ProfileMRU")
301 self.profileFileHistory.Save(self.config)
304 def updateProfileToControls(self):
305 self.scene.updateProfileToControls()
306 self.normalSettingsPanel.updateProfileToControls()
307 self.simpleSettingsPanel.updateProfileToControls()
309 def OnLoadProfile(self, e):
310 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)
311 dlg.SetWildcard("ini files (*.ini)|*.ini")
312 if dlg.ShowModal() == wx.ID_OK:
313 profileFile = dlg.GetPath()
314 profile.loadProfile(profileFile)
315 self.updateProfileToControls()
317 # Update the Profile MRU
318 self.addToProfileMRU(profileFile)
321 def OnLoadProfileFromGcode(self, e):
322 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)
323 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
324 if dlg.ShowModal() == wx.ID_OK:
325 gcodeFile = dlg.GetPath()
326 f = open(gcodeFile, 'r')
329 if line.startswith(';CURA_PROFILE_STRING:'):
330 profile.loadProfileFromString(line[line.find(':')+1:].strip())
333 self.updateProfileToControls()
335 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)
338 def OnSaveProfile(self, e):
339 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
340 dlg.SetWildcard("ini files (*.ini)|*.ini")
341 if dlg.ShowModal() == wx.ID_OK:
342 profileFile = dlg.GetPath()
343 profile.saveProfile(profileFile)
346 def OnResetProfile(self, e):
347 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)
348 result = dlg.ShowModal() == wx.ID_YES
351 profile.resetProfile()
352 self.updateProfileToControls()
354 def OnBatchRun(self, e):
355 br = batchRun.batchRunWindow(self)
359 def OnSimpleSwitch(self, e):
360 profile.putPreference('startMode', 'Simple')
361 self.updateSliceMode()
363 def OnNormalSwitch(self, e):
364 profile.putPreference('startMode', 'Normal')
365 self.updateSliceMode()
367 def OnDefaultMarlinFirmware(self, e):
368 firmwareInstall.InstallFirmware()
370 def OnCustomFirmware(self, e):
371 if profile.getPreference('machine_type') == 'ultimaker':
372 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)
373 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
374 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
375 if dlg.ShowModal() == wx.ID_OK:
376 filename = dlg.GetPath()
377 if not(os.path.exists(filename)):
379 #For some reason my Ubuntu 10.10 crashes here.
380 firmwareInstall.InstallFirmware(filename)
382 def OnFirstRunWizard(self, e):
383 configWizard.configWizard()
384 self.updateProfileToControls()
386 def OnBedLevelWizard(self, e):
387 configWizard.bedLevelWizard()
389 def OnExpertOpen(self, e):
390 ecw = expertConfig.expertConfigWindow()
393 self.scene.sceneUpdated()
395 def OnMinecraftImport(self, e):
396 mi = minecraftImport.minecraftImportWindow(self)
400 def OnSuperformula(self, e):
401 sf = superformula.superformulaWindow(self)
405 def OnCheckForUpdate(self, e):
406 newVersion = version.checkForNewerVersion()
407 if newVersion is not None:
408 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:
409 webbrowser.open(newVersion)
411 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
413 def OnClose(self, e):
414 profile.saveProfile(profile.getDefaultProfilePath())
416 # Save the window position, size & state from the preferences file
417 profile.putPreference('window_maximized', self.IsMaximized())
418 if not self.IsMaximized() and not self.IsIconized():
419 (posx, posy) = self.GetPosition()
420 profile.putPreference('window_pos_x', posx)
421 profile.putPreference('window_pos_y', posy)
422 (width, height) = self.GetSize()
423 profile.putPreference('window_width', width)
424 profile.putPreference('window_height', height)
426 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
427 isSimple = profile.getPreference('startMode') == 'Simple'
429 self.normalSashPos = self.splitter.GetSashPosition()
430 profile.putPreference('window_normal_sash', self.normalSashPos)
432 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
434 self.scene.OnPaint = lambda e : e
435 self.scene._slicer.cleanup()
441 class normalSettingsPanel(configBase.configPanelBase):
442 "Main user interface window"
443 def __init__(self, parent, callback = None):
444 super(normalSettingsPanel, self).__init__(parent, callback)
447 self.nb = wx.Notebook(self)
448 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
449 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
451 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
452 self._addSettingsToPanels('basic', left, right)
453 self.SizeLabelWidths(left, right)
455 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
456 self._addSettingsToPanels('advanced', left, right)
457 self.SizeLabelWidths(left, right)
460 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
461 if len(self.pluginPanel.pluginList) > 0 and False:
462 self.nb.AddPage(self.pluginPanel, "Plugins")
464 self.pluginPanel.Show(False)
467 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
468 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
470 self.Bind(wx.EVT_SIZE, self.OnSize)
472 self.nb.SetSize(self.GetSize())
473 self.UpdateSize(self.printPanel)
474 self.UpdateSize(self.advancedPanel)
476 def _addSettingsToPanels(self, category, left, right):
477 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
481 for title in profile.getSubCategoriesFor(category):
482 n += 1 + len(profile.getSettingsForCategory(category, title))
485 configBase.TitleRow(p, title)
486 for s in profile.getSettingsForCategory(category, title):
487 if s.checkConditions():
488 configBase.SettingRow(p, s.getName())
490 def SizeLabelWidths(self, left, right):
491 leftWidth = self.getLabelColumnWidth(left)
492 rightWidth = self.getLabelColumnWidth(right)
493 maxWidth = max(leftWidth, rightWidth)
494 self.setLabelColumnWidth(left, maxWidth)
495 self.setLabelColumnWidth(right, maxWidth)
498 # Make the size of the Notebook control the same size as this control
499 self.nb.SetSize(self.GetSize())
501 # Propegate the OnSize() event (just in case)
504 # Perform out resize magic
505 self.UpdateSize(self.printPanel)
506 self.UpdateSize(self.advancedPanel)
508 def UpdateSize(self, configPanel):
509 sizer = configPanel.GetSizer()
513 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
516 # if width(col1) > (best_width(col1) + best_width(col1)):
517 # switch to horizontal
520 col1 = configPanel.leftPanel
521 colSize1 = col1.GetSize()
522 colBestSize1 = col1.GetBestSize()
523 col2 = configPanel.rightPanel
524 colSize2 = col2.GetSize()
525 colBestSize2 = col2.GetBestSize()
527 orientation = sizer.GetOrientation()
529 if orientation == wx.HORIZONTAL:
530 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
532 sizer = wx.BoxSizer(wx.VERTICAL)
533 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
534 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
535 configPanel.SetSizer(sizer)
541 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
543 sizer = wx.BoxSizer(wx.HORIZONTAL)
544 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
545 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
546 configPanel.SetSizer(sizer)
552 def updateProfileToControls(self):
553 super(normalSettingsPanel, self).updateProfileToControls()
554 self.alterationPanel.updateProfileToControls()
555 self.pluginPanel.updateProfileToControls()