1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
9 from Cura.gui import configBase
10 from Cura.gui import expertConfig
11 from Cura.gui import alterationPanel
12 from Cura.gui import pluginPanel
13 from Cura.gui import preferencesDialog
14 from Cura.gui import configWizard
15 from Cura.gui import firmwareInstall
16 from Cura.gui import simpleMode
17 from Cura.gui import sceneView
18 from Cura.gui import aboutWindow
19 from Cura.gui.util import dropTarget
20 #from Cura.gui.tools import batchRun
21 from Cura.gui.tools import pidDebugger
22 from Cura.gui.tools import minecraftImport
23 from Cura.util import profile
24 from Cura.util import version
26 from Cura.util import meshLoader
29 #MacOS release currently lacks some wx components, like the Publisher.
30 from wx.lib.pubsub import Publisher
34 class mainWindow(wx.Frame):
36 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
38 wx.EVT_CLOSE(self, self.OnClose)
40 # allow dropping any file, restrict later
41 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles))
43 # TODO: wxWidgets 2.9.4 has a bug when NSView does not register for dragged types when wx drop target is set. It was fixed in 2.9.5
44 if sys.platform.startswith('darwin'):
47 nswindow = objc.objc_object(c_void_p=self.MacGetTopLevelWindowRef())
48 view = nswindow.contentView()
49 view.registerForDraggedTypes_([u'NSFilenamesPboardType'])
53 self.normalModeOnlyItems = []
55 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
56 self.config = wx.FileConfig(appName="Cura",
57 localFilename=mruFile,
58 style=wx.CONFIG_USE_LOCAL_FILE)
60 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)]
61 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
62 self.config.SetPath("/ModelMRU")
63 self.modelFileHistory.Load(self.config)
65 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)]
66 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
67 self.config.SetPath("/ProfileMRU")
68 self.profileFileHistory.Load(self.config)
70 self.menubar = wx.MenuBar()
71 self.fileMenu = wx.Menu()
72 i = self.fileMenu.Append(-1, _("Load model file...\tCTRL+L"))
73 self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i)
74 i = self.fileMenu.Append(-1, _("Save model...\tCTRL+S"))
75 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
76 i = self.fileMenu.Append(-1, _("Reload platform\tF5"))
77 self.Bind(wx.EVT_MENU, lambda e: self.scene.reloadScene(e), i)
78 i = self.fileMenu.Append(-1, _("Clear platform"))
79 self.Bind(wx.EVT_MENU, lambda e: self.scene.OnDeleteAll(e), i)
81 self.fileMenu.AppendSeparator()
82 i = self.fileMenu.Append(-1, _("Print...\tCTRL+P"))
83 self.Bind(wx.EVT_MENU, lambda e: self.scene.OnPrintButton(1), i)
84 i = self.fileMenu.Append(-1, _("Save GCode..."))
85 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
86 i = self.fileMenu.Append(-1, _("Show slice engine log..."))
87 self.Bind(wx.EVT_MENU, lambda e: self.scene._showEngineLog(), i)
89 self.fileMenu.AppendSeparator()
90 i = self.fileMenu.Append(-1, _("Open Profile..."))
91 self.normalModeOnlyItems.append(i)
92 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
93 i = self.fileMenu.Append(-1, _("Save Profile..."))
94 self.normalModeOnlyItems.append(i)
95 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
96 i = self.fileMenu.Append(-1, _("Load Profile from GCode..."))
97 self.normalModeOnlyItems.append(i)
98 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
99 self.fileMenu.AppendSeparator()
100 i = self.fileMenu.Append(-1, _("Reset Profile to default"))
101 self.normalModeOnlyItems.append(i)
102 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
104 self.fileMenu.AppendSeparator()
105 i = self.fileMenu.Append(-1, _("Preferences...\tCTRL+,"))
106 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
107 i = self.fileMenu.Append(-1, _("Machine settings..."))
108 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
109 self.fileMenu.AppendSeparator()
112 modelHistoryMenu = wx.Menu()
113 self.fileMenu.AppendMenu(wx.NewId(), '&' + _("Recent Model Files"), modelHistoryMenu)
114 self.modelFileHistory.UseMenu(modelHistoryMenu)
115 self.modelFileHistory.AddFilesToMenu()
116 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
119 profileHistoryMenu = wx.Menu()
120 self.fileMenu.AppendMenu(wx.NewId(), _("Recent Profile Files"), profileHistoryMenu)
121 self.profileFileHistory.UseMenu(profileHistoryMenu)
122 self.profileFileHistory.AddFilesToMenu()
123 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
125 self.fileMenu.AppendSeparator()
126 i = self.fileMenu.Append(wx.ID_EXIT, _("Quit"))
127 self.Bind(wx.EVT_MENU, self.OnQuit, i)
128 self.menubar.Append(self.fileMenu, '&' + _("File"))
130 toolsMenu = wx.Menu()
131 #i = toolsMenu.Append(-1, 'Batch run...')
132 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
133 #self.normalModeOnlyItems.append(i)
135 if minecraftImport.hasMinecraft():
136 i = toolsMenu.Append(-1, _("Minecraft map import..."))
137 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
139 if version.isDevVersion():
140 i = toolsMenu.Append(-1, _("PID Debugger..."))
141 self.Bind(wx.EVT_MENU, self.OnPIDDebugger, i)
142 i = toolsMenu.Append(-1, _("Auto Firmware Update..."))
143 self.Bind(wx.EVT_MENU, self.OnAutoFirmwareUpdate, i)
145 i = toolsMenu.Append(-1, _("Copy profile to clipboard"))
146 self.Bind(wx.EVT_MENU, self.onCopyProfileClipboard,i)
148 toolsMenu.AppendSeparator()
149 self.allAtOnceItem = toolsMenu.Append(-1, _("Print all at once"), kind=wx.ITEM_RADIO)
150 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.allAtOnceItem)
151 self.oneAtATime = toolsMenu.Append(-1, _("Print one at a time"), kind=wx.ITEM_RADIO)
152 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.oneAtATime)
153 if profile.getPreference('oneAtATime') == 'True':
154 self.oneAtATime.Check(True)
156 self.allAtOnceItem.Check(True)
158 self.menubar.Append(toolsMenu, _("Tools"))
160 #Machine menu for machine configuration/tooling
161 self.machineMenu = wx.Menu()
162 self.updateMachineMenu()
164 self.menubar.Append(self.machineMenu, _("Machine"))
166 expertMenu = wx.Menu()
167 i = expertMenu.Append(-1, _("Switch to quickprint..."), kind=wx.ITEM_RADIO)
168 self.switchToQuickprintMenuItem = i
169 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
171 i = expertMenu.Append(-1, _("Switch to full settings..."), kind=wx.ITEM_RADIO)
172 self.switchToNormalMenuItem = i
173 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
174 expertMenu.AppendSeparator()
176 i = expertMenu.Append(-1, _("Open expert settings...\tCTRL+E"))
177 self.normalModeOnlyItems.append(i)
178 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
179 expertMenu.AppendSeparator()
180 self.bedLevelWizardMenuItem = expertMenu.Append(-1, _("Run bed leveling wizard..."))
181 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, self.bedLevelWizardMenuItem)
182 self.headOffsetWizardMenuItem = expertMenu.Append(-1, _("Run head offset wizard..."))
183 self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, self.headOffsetWizardMenuItem)
185 self.menubar.Append(expertMenu, _("Expert"))
188 i = helpMenu.Append(-1, _("Online documentation..."))
189 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
190 i = helpMenu.Append(-1, _("Report a problem..."))
191 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
192 i = helpMenu.Append(-1, _("Check for update..."))
193 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
194 i = helpMenu.Append(-1, _("Open YouMagine website..."))
195 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://www.youmagine.com/'), i)
196 i = helpMenu.Append(-1, _("About Cura..."))
197 self.Bind(wx.EVT_MENU, self.OnAbout, i)
198 self.menubar.Append(helpMenu, _("Help"))
199 self.SetMenuBar(self.menubar)
201 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
202 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
203 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
204 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
207 self.scene = sceneView.SceneView(self.rightPane)
210 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, self.scene.sceneUpdated)
211 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, self.scene.sceneUpdated)
213 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
214 self.leftSizer.Add(self.simpleSettingsPanel, 1)
215 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
216 self.leftPane.SetSizer(self.leftSizer)
218 #Main sizer, to position the preview window, buttons and tab control
219 sizer = wx.BoxSizer()
220 self.rightPane.SetSizer(sizer)
221 sizer.Add(self.scene, 1, flag=wx.EXPAND)
224 sizer = wx.BoxSizer(wx.VERTICAL)
226 sizer.Add(self.splitter, 1, wx.EXPAND)
230 self.updateProfileToAllControls()
232 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
234 self.simpleSettingsPanel.Show(False)
235 self.normalSettingsPanel.Show(False)
237 # Set default window size & position
238 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
241 #Timer set; used to check if profile is on the clipboard
242 self.timer = wx.Timer(self)
243 self.Bind(wx.EVT_TIMER, self.onTimer)
244 self.timer.Start(1000)
245 self.lastTriedClipboard = profile.getProfileString()
247 # Restore the window position, size & state from the preferences file
249 if profile.getPreference('window_maximized') == 'True':
252 posx = int(profile.getPreference('window_pos_x'))
253 posy = int(profile.getPreference('window_pos_y'))
254 width = int(profile.getPreference('window_width'))
255 height = int(profile.getPreference('window_height'))
256 if posx > 0 or posy > 0:
257 self.SetPosition((posx,posy))
258 if width > 0 and height > 0:
259 self.SetSize((width,height))
261 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
263 self.normalSashPos = 0
265 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
266 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
268 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
270 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
272 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
274 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
275 self.SetSize((800,600))
278 self.updateSliceMode()
279 self.scene.SetFocus()
280 self.dialogframe = None
281 if Publisher is not None:
282 Publisher().subscribe(self.onPluginUpdate, "pluginupdate")
284 def onPluginUpdate(self,msg): #receives commands from the plugin thread
285 cmd = str(msg.data).split(";")
286 if cmd[0] == "OpenPluginProgressWindow":
287 if len(cmd)==1: #no titel received
289 if len(cmd)<3: #no message text received
290 cmd.append("Plugin is executed...")
293 self.dialogframe = wx.Frame(self, -1, cmd[1],pos = ((wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)-dialogwidth)/2,(wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)-dialogheight)/2), size=(dialogwidth,dialogheight), style = wx.STAY_ON_TOP)
294 self.dialogpanel = wx.Panel(self.dialogframe, -1, pos = (0,0), size = (dialogwidth,dialogheight))
295 self.dlgtext = wx.StaticText(self.dialogpanel, label = cmd[2], pos = (10,10), size = (280,40))
296 self.dlgbar = wx.Gauge(self.dialogpanel,-1, 100, pos = (10,50), size = (280,20), style = wx.GA_HORIZONTAL)
297 self.dialogframe.Show()
299 elif cmd[0] == "Progress":
301 if number <= 100 and self.dialogframe is not None:
302 self.dlgbar.SetValue(number)
304 self.dlgbar.SetValue(100)
305 elif cmd[0] == "ClosePluginProgressWindow":
306 self.dialogframe.Destroy()
307 self.dialogframe=None
309 print "Unknown Plugin update received: " + cmd[0]
311 def onTimer(self, e):
312 #Check if there is something in the clipboard
315 if not wx.TheClipboard.IsOpened():
316 if not wx.TheClipboard.Open():
318 do = wx.TextDataObject()
319 if wx.TheClipboard.GetData(do):
320 profileString = do.GetText()
321 wx.TheClipboard.Close()
323 startTag = "CURA_PROFILE_STRING:"
324 if startTag in profileString:
325 #print "Found correct syntax on clipboard"
326 profileString = profileString.replace("\n","").strip()
327 profileString = profileString[profileString.find(startTag)+len(startTag):]
328 if profileString != self.lastTriedClipboard:
330 self.lastTriedClipboard = profileString
331 profile.setProfileFromString(profileString)
332 self.scene.notification.message("Loaded new profile from clipboard.")
333 self.updateProfileToAllControls()
335 print "Unable to read from clipboard"
338 def updateSliceMode(self):
339 isSimple = profile.getPreference('startMode') == 'Simple'
341 self.normalSettingsPanel.Show(not isSimple)
342 self.simpleSettingsPanel.Show(isSimple)
343 self.leftPane.Layout()
345 for i in self.normalModeOnlyItems:
346 i.Enable(not isSimple)
348 self.switchToQuickprintMenuItem.Check()
350 self.switchToNormalMenuItem.Check()
352 # Set splitter sash position & size
354 # Save normal mode sash
355 self.normalSashPos = self.splitter.GetSashPosition()
357 # Change location of sash to width of quick mode pane
358 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
359 self.splitter.SetSashPosition(width, True)
362 self.splitter.SetSashSize(0)
364 self.splitter.SetSashPosition(self.normalSashPos, True)
366 self.splitter.SetSashSize(4)
367 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
368 if profile.getMachineSetting('machine_type') == 'ultimaker2':
369 self.bedLevelWizardMenuItem.Enable(False)
370 self.headOffsetWizardMenuItem.Enable(False)
371 if int(profile.getMachineSetting('extruder_amount')) < 2:
372 self.headOffsetWizardMenuItem.Enable(False)
373 self.scene.updateProfileToControls()
374 self.scene._scene.pushFree()
376 def onOneAtATimeSwitch(self, e):
377 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
378 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
379 wx.MessageBox(_('For "One at a time" printing, you need to have entered the correct head size and gantry height in the machine settings'), _('One at a time warning'), wx.OK | wx.ICON_WARNING)
380 self.scene.updateProfileToControls()
381 self.scene._scene.pushFree()
382 self.scene.sceneUpdated()
384 def OnPreferences(self, e):
385 prefDialog = preferencesDialog.preferencesDialog(self)
389 wx.CallAfter(prefDialog.Show)
391 def OnMachineSettings(self, e):
392 prefDialog = preferencesDialog.machineSettingsDialog(self)
397 def OnDropFiles(self, files):
399 self.updateProfileToAllControls()
400 self.scene.loadFiles(files)
402 def OnModelMRU(self, e):
403 fileNum = e.GetId() - self.ID_MRU_MODEL1
404 path = self.modelFileHistory.GetHistoryFile(fileNum)
406 self.modelFileHistory.AddFileToHistory(path) # move up the list
407 self.config.SetPath("/ModelMRU")
408 self.modelFileHistory.Save(self.config)
411 profile.putPreference('lastFile', path)
413 self.scene.loadFiles(filelist)
415 def addToModelMRU(self, file):
416 self.modelFileHistory.AddFileToHistory(file)
417 self.config.SetPath("/ModelMRU")
418 self.modelFileHistory.Save(self.config)
421 def OnProfileMRU(self, e):
422 fileNum = e.GetId() - self.ID_MRU_PROFILE1
423 path = self.profileFileHistory.GetHistoryFile(fileNum)
425 self.profileFileHistory.AddFileToHistory(path) # move up the list
426 self.config.SetPath("/ProfileMRU")
427 self.profileFileHistory.Save(self.config)
430 profile.loadProfile(path)
431 self.updateProfileToAllControls()
433 def addToProfileMRU(self, file):
434 self.profileFileHistory.AddFileToHistory(file)
435 self.config.SetPath("/ProfileMRU")
436 self.profileFileHistory.Save(self.config)
439 def updateProfileToAllControls(self):
440 self.scene.updateProfileToControls()
441 self.normalSettingsPanel.updateProfileToControls()
442 self.simpleSettingsPanel.updateProfileToControls()
444 def reloadSettingPanels(self):
445 self.leftSizer.Detach(self.simpleSettingsPanel)
446 self.leftSizer.Detach(self.normalSettingsPanel)
447 self.simpleSettingsPanel.Destroy()
448 self.normalSettingsPanel.Destroy()
449 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
450 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
451 self.leftSizer.Add(self.simpleSettingsPanel, 1)
452 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
453 self.updateSliceMode()
454 self.updateProfileToAllControls()
456 def updateMachineMenu(self):
457 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
458 for item in self.machineMenu.GetMenuItems():
459 self.machineMenu.RemoveItem(item)
461 #Add a menu item for each machine configuration.
462 for n in xrange(0, profile.getMachineCount()):
463 i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
464 if n == int(profile.getPreferenceFloat('active_machine')):
466 self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
468 self.machineMenu.AppendSeparator()
469 i = self.machineMenu.Append(-1, _("Add new machine..."))
470 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
471 i = self.machineMenu.Append(-1, _("Machine settings..."))
472 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
474 #Add tools for machines.
475 self.machineMenu.AppendSeparator()
477 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
478 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
480 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
481 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
483 def OnLoadProfile(self, e):
484 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)
485 dlg.SetWildcard("ini files (*.ini)|*.ini")
486 if dlg.ShowModal() == wx.ID_OK:
487 profileFile = dlg.GetPath()
488 profile.loadProfile(profileFile)
489 self.updateProfileToAllControls()
491 # Update the Profile MRU
492 self.addToProfileMRU(profileFile)
495 def OnLoadProfileFromGcode(self, e):
496 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)
497 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
498 if dlg.ShowModal() == wx.ID_OK:
499 gcodeFile = dlg.GetPath()
500 f = open(gcodeFile, 'r')
503 if line.startswith(';CURA_PROFILE_STRING:'):
504 profile.setProfileFromString(line[line.find(':')+1:].strip())
505 if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
506 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
509 self.updateProfileToAllControls()
511 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)
514 def OnSaveProfile(self, e):
515 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
516 dlg.SetWildcard("ini files (*.ini)|*.ini")
517 if dlg.ShowModal() == wx.ID_OK:
518 profileFile = dlg.GetPath()
519 if not profileFile.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
520 profileFile += '.ini'
521 profile.saveProfile(profileFile)
524 def OnResetProfile(self, e):
525 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)
526 result = dlg.ShowModal() == wx.ID_YES
529 profile.resetProfile()
530 self.updateProfileToAllControls()
532 def OnSimpleSwitch(self, e):
533 profile.putPreference('startMode', 'Simple')
534 self.updateSliceMode()
536 def OnNormalSwitch(self, e):
537 profile.putPreference('startMode', 'Normal')
538 self.updateSliceMode()
540 def OnDefaultMarlinFirmware(self, e):
541 firmwareInstall.InstallFirmware(self)
543 def OnCustomFirmware(self, e):
544 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
545 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)
546 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
547 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
548 if dlg.ShowModal() == wx.ID_OK:
549 filename = dlg.GetPath()
551 if not(os.path.exists(filename)):
553 #For some reason my Ubuntu 10.10 crashes here.
554 firmwareInstall.InstallFirmware(self, filename)
556 def OnAddNewMachine(self, e):
558 configWizard.ConfigWizard(True)
560 self.reloadSettingPanels()
561 self.updateMachineMenu()
563 def OnSelectMachine(self, index):
564 profile.setActiveMachine(index)
565 self.reloadSettingPanels()
567 def OnBedLevelWizard(self, e):
568 configWizard.bedLevelWizard()
570 def OnHeadOffsetWizard(self, e):
571 configWizard.headOffsetWizard()
573 def OnExpertOpen(self, e):
574 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
578 def OnMinecraftImport(self, e):
579 mi = minecraftImport.minecraftImportWindow(self)
583 def OnPIDDebugger(self, e):
584 debugger = pidDebugger.debuggerWindow(self)
588 def OnAutoFirmwareUpdate(self, e):
589 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
590 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
591 if dlg.ShowModal() == wx.ID_OK:
592 filename = dlg.GetPath()
594 if not(os.path.exists(filename)):
596 #For some reason my Ubuntu 10.10 crashes here.
597 installer = firmwareInstall.AutoUpdateFirmware(self, filename)
599 def onCopyProfileClipboard(self, e):
601 if not wx.TheClipboard.IsOpened():
602 wx.TheClipboard.Open()
603 clipData = wx.TextDataObject()
604 self.lastTriedClipboard = profile.getProfileString()
605 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
606 clipData.SetText(profileString)
607 wx.TheClipboard.SetData(clipData)
608 wx.TheClipboard.Close()
610 print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
612 def OnCheckForUpdate(self, e):
613 newVersion = version.checkForNewerVersion()
614 if newVersion is not None:
615 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:
616 webbrowser.open(newVersion)
618 wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
620 def OnAbout(self, e):
621 aboutBox = aboutWindow.aboutWindow()
625 def OnClose(self, e):
626 profile.saveProfile(profile.getDefaultProfilePath(), True)
628 # Save the window position, size & state from the preferences file
629 profile.putPreference('window_maximized', self.IsMaximized())
630 if not self.IsMaximized() and not self.IsIconized():
631 (posx, posy) = self.GetPosition()
632 profile.putPreference('window_pos_x', posx)
633 profile.putPreference('window_pos_y', posy)
634 (width, height) = self.GetSize()
635 profile.putPreference('window_width', width)
636 profile.putPreference('window_height', height)
638 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
639 isSimple = profile.getPreference('startMode') == 'Simple'
641 self.normalSashPos = self.splitter.GetSashPosition()
642 profile.putPreference('window_normal_sash', self.normalSashPos)
644 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
646 self.scene.OnPaint = lambda e : e
647 self.scene._engine.cleanup()
653 class normalSettingsPanel(configBase.configPanelBase):
654 "Main user interface window"
655 def __init__(self, parent, callback = None):
656 super(normalSettingsPanel, self).__init__(parent, callback)
659 self.nb = wx.Notebook(self)
660 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
661 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
663 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, _('Basic'))
664 self._addSettingsToPanels('basic', left, right)
665 self.SizeLabelWidths(left, right)
667 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, _('Advanced'))
668 self._addSettingsToPanels('advanced', left, right)
669 self.SizeLabelWidths(left, right)
672 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
673 self.nb.AddPage(self.pluginPanel, _("Plugins"))
676 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
677 self.alterationPanel = None
679 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
680 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
682 self.Bind(wx.EVT_SIZE, self.OnSize)
684 self.nb.SetSize(self.GetSize())
685 self.UpdateSize(self.printPanel)
686 self.UpdateSize(self.advancedPanel)
688 def _addSettingsToPanels(self, category, left, right):
689 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
693 for title in profile.getSubCategoriesFor(category):
694 n += 1 + len(profile.getSettingsForCategory(category, title))
697 configBase.TitleRow(p, _(title))
698 for s in profile.getSettingsForCategory(category, title):
699 configBase.SettingRow(p, s.getName())
701 def SizeLabelWidths(self, left, right):
702 leftWidth = self.getLabelColumnWidth(left)
703 rightWidth = self.getLabelColumnWidth(right)
704 maxWidth = max(leftWidth, rightWidth)
705 self.setLabelColumnWidth(left, maxWidth)
706 self.setLabelColumnWidth(right, maxWidth)
709 # Make the size of the Notebook control the same size as this control
710 self.nb.SetSize(self.GetSize())
712 # Propegate the OnSize() event (just in case)
715 # Perform out resize magic
716 self.UpdateSize(self.printPanel)
717 self.UpdateSize(self.advancedPanel)
719 def UpdateSize(self, configPanel):
720 sizer = configPanel.GetSizer()
724 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
727 # if width(col1) > (best_width(col1) + best_width(col1)):
728 # switch to horizontal
731 col1 = configPanel.leftPanel
732 colSize1 = col1.GetSize()
733 colBestSize1 = col1.GetBestSize()
734 col2 = configPanel.rightPanel
735 colSize2 = col2.GetSize()
736 colBestSize2 = col2.GetBestSize()
738 orientation = sizer.GetOrientation()
740 if orientation == wx.HORIZONTAL:
741 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
743 sizer = wx.BoxSizer(wx.VERTICAL)
744 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
745 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
746 configPanel.SetSizer(sizer)
752 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
754 sizer = wx.BoxSizer(wx.HORIZONTAL)
755 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
756 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
757 configPanel.SetSizer(sizer)
763 def updateProfileToControls(self):
764 super(normalSettingsPanel, self).updateProfileToControls()
765 if self.alterationPanel is not None:
766 self.alterationPanel.updateProfileToControls()
767 self.pluginPanel.updateProfileToControls()