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)
400 #sf = superformula.superformulaEvolver(self)
404 def OnCheckForUpdate(self, e):
405 newVersion = version.checkForNewerVersion()
406 if newVersion is not None:
407 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:
408 webbrowser.open(newVersion)
410 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
412 def OnClose(self, e):
413 profile.saveProfile(profile.getDefaultProfilePath())
415 # Save the window position, size & state from the preferences file
416 profile.putPreference('window_maximized', self.IsMaximized())
417 if not self.IsMaximized() and not self.IsIconized():
418 (posx, posy) = self.GetPosition()
419 profile.putPreference('window_pos_x', posx)
420 profile.putPreference('window_pos_y', posy)
421 (width, height) = self.GetSize()
422 profile.putPreference('window_width', width)
423 profile.putPreference('window_height', height)
425 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
426 isSimple = profile.getPreference('startMode') == 'Simple'
428 self.normalSashPos = self.splitter.GetSashPosition()
429 profile.putPreference('window_normal_sash', self.normalSashPos)
431 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
433 self.scene.OnPaint = lambda e : e
434 self.scene._slicer.cleanup()
440 class normalSettingsPanel(configBase.configPanelBase):
441 "Main user interface window"
442 def __init__(self, parent, callback = None):
443 super(normalSettingsPanel, self).__init__(parent, callback)
446 self.nb = wx.Notebook(self)
447 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
448 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
450 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
451 self._addSettingsToPanels('basic', left, right)
452 self.SizeLabelWidths(left, right)
454 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
455 self._addSettingsToPanels('advanced', left, right)
456 self.SizeLabelWidths(left, right)
459 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
460 if len(self.pluginPanel.pluginList) > 0:
461 self.nb.AddPage(self.pluginPanel, "Plugins")
463 self.pluginPanel.Show(False)
466 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
467 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
469 self.Bind(wx.EVT_SIZE, self.OnSize)
471 self.nb.SetSize(self.GetSize())
472 self.UpdateSize(self.printPanel)
473 self.UpdateSize(self.advancedPanel)
475 def _addSettingsToPanels(self, category, left, right):
476 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
480 for title in profile.getSubCategoriesFor(category):
481 n += 1 + len(profile.getSettingsForCategory(category, title))
484 configBase.TitleRow(p, title)
485 for s in profile.getSettingsForCategory(category, title):
486 if s.checkConditions():
487 configBase.SettingRow(p, s.getName())
489 def SizeLabelWidths(self, left, right):
490 leftWidth = self.getLabelColumnWidth(left)
491 rightWidth = self.getLabelColumnWidth(right)
492 maxWidth = max(leftWidth, rightWidth)
493 self.setLabelColumnWidth(left, maxWidth)
494 self.setLabelColumnWidth(right, maxWidth)
497 # Make the size of the Notebook control the same size as this control
498 self.nb.SetSize(self.GetSize())
500 # Propegate the OnSize() event (just in case)
503 # Perform out resize magic
504 self.UpdateSize(self.printPanel)
505 self.UpdateSize(self.advancedPanel)
507 def UpdateSize(self, configPanel):
508 sizer = configPanel.GetSizer()
512 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
515 # if width(col1) > (best_width(col1) + best_width(col1)):
516 # switch to horizontal
519 col1 = configPanel.leftPanel
520 colSize1 = col1.GetSize()
521 colBestSize1 = col1.GetBestSize()
522 col2 = configPanel.rightPanel
523 colSize2 = col2.GetSize()
524 colBestSize2 = col2.GetBestSize()
526 orientation = sizer.GetOrientation()
528 if orientation == wx.HORIZONTAL:
529 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
531 sizer = wx.BoxSizer(wx.VERTICAL)
532 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
533 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
534 configPanel.SetSizer(sizer)
540 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
542 sizer = wx.BoxSizer(wx.HORIZONTAL)
543 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
544 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
545 configPanel.SetSizer(sizer)
551 def updateProfileToControls(self):
552 super(normalSettingsPanel, self).updateProfileToControls()
553 self.alterationPanel.updateProfileToControls()
554 self.pluginPanel.updateProfileToControls()