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)
57 i = self.fileMenu.Append(-1, 'Save GCode...')
58 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), 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, 'Batch run...')
108 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
109 #self.normalModeOnlyItems.append(i)
110 if minecraftImport.hasMinecraft():
111 i = toolsMenu.Append(-1, 'Minecraft import...')
112 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
113 i = toolsMenu.Append(-1, 'Super-shaper...')
114 self.Bind(wx.EVT_MENU, self.OnSuperformula, i)
115 self.menubar.Append(toolsMenu, 'Tools')
117 expertMenu = wx.Menu()
118 i = expertMenu.Append(-1, 'Open expert settings...')
119 self.normalModeOnlyItems.append(i)
120 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
121 expertMenu.AppendSeparator()
122 if firmwareInstall.getDefaultFirmware() is not None:
123 i = expertMenu.Append(-1, 'Install default Marlin firmware')
124 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
125 i = expertMenu.Append(-1, 'Install custom firmware')
126 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
127 expertMenu.AppendSeparator()
128 i = expertMenu.Append(-1, 'Run first run wizard...')
129 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
130 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
131 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
132 self.menubar.Append(expertMenu, 'Expert')
135 i = helpMenu.Append(-1, 'Online documentation...')
136 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
137 i = helpMenu.Append(-1, 'Report a problem...')
138 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
139 i = helpMenu.Append(-1, 'Check for update...')
140 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
141 self.menubar.Append(helpMenu, 'Help')
142 self.SetMenuBar(self.menubar)
144 if profile.getPreference('lastFile') != '':
145 self.filelist = profile.getPreference('lastFile').split(';')
149 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
150 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
151 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
152 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
155 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
156 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
158 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
159 self.leftSizer.Add(self.simpleSettingsPanel)
160 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
161 self.leftPane.SetSizer(self.leftSizer)
164 self.scene = sceneView.SceneView(self.rightPane)
166 #Main sizer, to position the preview window, buttons and tab control
167 sizer = wx.BoxSizer()
168 self.rightPane.SetSizer(sizer)
169 sizer.Add(self.scene, 1, flag=wx.EXPAND)
172 sizer = wx.BoxSizer(wx.VERTICAL)
174 sizer.Add(self.splitter, 1, wx.EXPAND)
178 if len(self.filelist) > 0:
179 self.scene.loadScene(self.filelist)
181 # Update the Model MRU
182 for idx in xrange(0, len(self.filelist)):
183 self.addToModelMRU(self.filelist[idx])
185 self.updateProfileToControls()
187 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
189 self.simpleSettingsPanel.Show(False)
190 self.normalSettingsPanel.Show(False)
192 # Set default window size & position
193 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
196 # Restore the window position, size & state from the preferences file
198 if profile.getPreference('window_maximized') == 'True':
201 posx = int(profile.getPreference('window_pos_x'))
202 posy = int(profile.getPreference('window_pos_y'))
203 width = int(profile.getPreference('window_width'))
204 height = int(profile.getPreference('window_height'))
205 if posx > 0 or posy > 0:
206 self.SetPosition((posx,posy))
207 if width > 0 and height > 0:
208 self.SetSize((width,height))
210 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
212 self.normalSashPos = 0
214 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
215 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
217 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
219 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
221 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
223 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
224 self.SetSize((800,600))
227 self.updateSliceMode()
231 def updateSliceMode(self):
232 isSimple = profile.getPreference('startMode') == 'Simple'
234 self.normalSettingsPanel.Show(not isSimple)
235 self.simpleSettingsPanel.Show(isSimple)
236 self.leftPane.Layout()
238 for i in self.normalModeOnlyItems:
239 i.Enable(not isSimple)
240 self.switchToQuickprintMenuItem.Enable(not isSimple)
241 self.switchToNormalMenuItem.Enable(isSimple)
243 # Set splitter sash position & size
245 # Save normal mode sash
246 self.normalSashPos = self.splitter.GetSashPosition()
248 # Change location of sash to width of quick mode pane
249 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
250 self.splitter.SetSashPosition(width, True)
253 self.splitter.SetSashSize(0)
255 self.splitter.SetSashPosition(self.normalSashPos, True)
257 self.splitter.SetSashSize(4)
258 self.scene.updateProfileToControls()
260 def OnPreferences(self, e):
261 prefDialog = preferencesDialog.preferencesDialog(self)
265 def OnDropFiles(self, files):
266 profile.setPluginConfig([])
267 self.updateProfileToControls()
268 self.scene.loadScene(files)
270 def OnModelMRU(self, e):
271 fileNum = e.GetId() - self.ID_MRU_MODEL1
272 path = self.modelFileHistory.GetHistoryFile(fileNum)
274 self.modelFileHistory.AddFileToHistory(path) # move up the list
275 self.config.SetPath("/ModelMRU")
276 self.modelFileHistory.Save(self.config)
280 self.scene.loadScene(filelist)
282 def addToModelMRU(self, file):
283 self.modelFileHistory.AddFileToHistory(file)
284 self.config.SetPath("/ModelMRU")
285 self.modelFileHistory.Save(self.config)
288 def OnProfileMRU(self, e):
289 fileNum = e.GetId() - self.ID_MRU_PROFILE1
290 path = self.profileFileHistory.GetHistoryFile(fileNum)
292 self.profileFileHistory.AddFileToHistory(path) # move up the list
293 self.config.SetPath("/ProfileMRU")
294 self.profileFileHistory.Save(self.config)
297 profile.loadProfile(path)
298 self.updateProfileToControls()
300 def addToProfileMRU(self, file):
301 self.profileFileHistory.AddFileToHistory(file)
302 self.config.SetPath("/ProfileMRU")
303 self.profileFileHistory.Save(self.config)
306 def updateProfileToControls(self):
307 self.scene.updateProfileToControls()
308 self.normalSettingsPanel.updateProfileToControls()
309 self.simpleSettingsPanel.updateProfileToControls()
311 def OnLoadProfile(self, e):
312 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)
313 dlg.SetWildcard("ini files (*.ini)|*.ini")
314 if dlg.ShowModal() == wx.ID_OK:
315 profileFile = dlg.GetPath()
316 profile.loadProfile(profileFile)
317 self.updateProfileToControls()
319 # Update the Profile MRU
320 self.addToProfileMRU(profileFile)
323 def OnLoadProfileFromGcode(self, e):
324 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)
325 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
326 if dlg.ShowModal() == wx.ID_OK:
327 gcodeFile = dlg.GetPath()
328 f = open(gcodeFile, 'r')
331 if line.startswith(';CURA_PROFILE_STRING:'):
332 profile.loadProfileFromString(line[line.find(':')+1:].strip())
335 self.updateProfileToControls()
337 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)
340 def OnSaveProfile(self, e):
341 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
342 dlg.SetWildcard("ini files (*.ini)|*.ini")
343 if dlg.ShowModal() == wx.ID_OK:
344 profileFile = dlg.GetPath()
345 profile.saveProfile(profileFile)
348 def OnResetProfile(self, e):
349 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)
350 result = dlg.ShowModal() == wx.ID_YES
353 profile.resetProfile()
354 self.updateProfileToControls()
356 def OnBatchRun(self, e):
357 br = batchRun.batchRunWindow(self)
361 def OnSimpleSwitch(self, e):
362 profile.putPreference('startMode', 'Simple')
363 self.updateSliceMode()
365 def OnNormalSwitch(self, e):
366 profile.putPreference('startMode', 'Normal')
367 self.updateSliceMode()
369 def OnDefaultMarlinFirmware(self, e):
370 firmwareInstall.InstallFirmware()
372 def OnCustomFirmware(self, e):
373 if profile.getPreference('machine_type') == 'ultimaker':
374 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)
375 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
376 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
377 if dlg.ShowModal() == wx.ID_OK:
378 filename = dlg.GetPath()
379 if not(os.path.exists(filename)):
381 #For some reason my Ubuntu 10.10 crashes here.
382 firmwareInstall.InstallFirmware(filename)
384 def OnFirstRunWizard(self, e):
385 configWizard.configWizard()
386 self.updateProfileToControls()
388 def OnBedLevelWizard(self, e):
389 configWizard.bedLevelWizard()
391 def OnExpertOpen(self, e):
392 ecw = expertConfig.expertConfigWindow()
395 self.scene.sceneUpdated()
397 def OnMinecraftImport(self, e):
398 mi = minecraftImport.minecraftImportWindow(self)
402 def OnSuperformula(self, e):
403 sf = superformula.superformulaWindow(self)
407 def OnCheckForUpdate(self, e):
408 newVersion = version.checkForNewerVersion()
409 if newVersion is not None:
410 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:
411 webbrowser.open(newVersion)
413 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
415 def OnClose(self, e):
416 profile.saveProfile(profile.getDefaultProfilePath())
418 # Save the window position, size & state from the preferences file
419 profile.putPreference('window_maximized', self.IsMaximized())
420 if not self.IsMaximized() and not self.IsIconized():
421 (posx, posy) = self.GetPosition()
422 profile.putPreference('window_pos_x', posx)
423 profile.putPreference('window_pos_y', posy)
424 (width, height) = self.GetSize()
425 profile.putPreference('window_width', width)
426 profile.putPreference('window_height', height)
428 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
429 isSimple = profile.getPreference('startMode') == 'Simple'
431 self.normalSashPos = self.splitter.GetSashPosition()
432 profile.putPreference('window_normal_sash', self.normalSashPos)
434 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
436 self.scene.OnPaint = lambda e : e
437 self.scene._slicer.cleanup()
443 class normalSettingsPanel(configBase.configPanelBase):
444 "Main user interface window"
445 def __init__(self, parent, callback = None):
446 super(normalSettingsPanel, self).__init__(parent, callback)
449 self.nb = wx.Notebook(self)
450 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
451 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
453 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
454 self._addSettingsToPanels('basic', left, right)
455 self.SizeLabelWidths(left, right)
457 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
458 self._addSettingsToPanels('advanced', left, right)
459 self.SizeLabelWidths(left, right)
462 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
463 if len(self.pluginPanel.pluginList) > 0 and False:
464 self.nb.AddPage(self.pluginPanel, "Plugins")
466 self.pluginPanel.Show(False)
469 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
470 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
472 self.Bind(wx.EVT_SIZE, self.OnSize)
474 self.nb.SetSize(self.GetSize())
475 self.UpdateSize(self.printPanel)
476 self.UpdateSize(self.advancedPanel)
478 def _addSettingsToPanels(self, category, left, right):
479 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
483 for title in profile.getSubCategoriesFor(category):
484 n += 1 + len(profile.getSettingsForCategory(category, title))
487 configBase.TitleRow(p, title)
488 for s in profile.getSettingsForCategory(category, title):
489 if s.checkConditions():
490 configBase.SettingRow(p, s.getName())
492 def SizeLabelWidths(self, left, right):
493 leftWidth = self.getLabelColumnWidth(left)
494 rightWidth = self.getLabelColumnWidth(right)
495 maxWidth = max(leftWidth, rightWidth)
496 self.setLabelColumnWidth(left, maxWidth)
497 self.setLabelColumnWidth(right, maxWidth)
500 # Make the size of the Notebook control the same size as this control
501 self.nb.SetSize(self.GetSize())
503 # Propegate the OnSize() event (just in case)
506 # Perform out resize magic
507 self.UpdateSize(self.printPanel)
508 self.UpdateSize(self.advancedPanel)
510 def UpdateSize(self, configPanel):
511 sizer = configPanel.GetSizer()
515 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
518 # if width(col1) > (best_width(col1) + best_width(col1)):
519 # switch to horizontal
522 col1 = configPanel.leftPanel
523 colSize1 = col1.GetSize()
524 colBestSize1 = col1.GetBestSize()
525 col2 = configPanel.rightPanel
526 colSize2 = col2.GetSize()
527 colBestSize2 = col2.GetBestSize()
529 orientation = sizer.GetOrientation()
531 if orientation == wx.HORIZONTAL:
532 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
534 sizer = wx.BoxSizer(wx.VERTICAL)
535 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
536 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
537 configPanel.SetSizer(sizer)
543 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
545 sizer = wx.BoxSizer(wx.HORIZONTAL)
546 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
547 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
548 configPanel.SetSizer(sizer)
554 def updateProfileToControls(self):
555 super(normalSettingsPanel, self).updateProfileToControls()
556 self.alterationPanel.updateProfileToControls()
557 self.pluginPanel.updateProfileToControls()