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 if version.isDevVersion():
97 i = self.fileMenu.Append(-1, "Save difference from default...")
98 self.normalModeOnlyItems.append(i)
99 self.Bind(wx.EVT_MENU, self.OnSaveDifferences, i)
100 i = self.fileMenu.Append(-1, _("Load Profile from GCode..."))
101 self.normalModeOnlyItems.append(i)
102 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
103 self.fileMenu.AppendSeparator()
104 i = self.fileMenu.Append(-1, _("Reset Profile to default"))
105 self.normalModeOnlyItems.append(i)
106 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
108 self.fileMenu.AppendSeparator()
109 i = self.fileMenu.Append(-1, _("Preferences...\tCTRL+,"))
110 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
111 i = self.fileMenu.Append(-1, _("Machine settings..."))
112 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
113 self.fileMenu.AppendSeparator()
116 modelHistoryMenu = wx.Menu()
117 self.fileMenu.AppendMenu(wx.NewId(), '&' + _("Recent Model Files"), modelHistoryMenu)
118 self.modelFileHistory.UseMenu(modelHistoryMenu)
119 self.modelFileHistory.AddFilesToMenu()
120 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
123 profileHistoryMenu = wx.Menu()
124 self.fileMenu.AppendMenu(wx.NewId(), _("Recent Profile Files"), profileHistoryMenu)
125 self.profileFileHistory.UseMenu(profileHistoryMenu)
126 self.profileFileHistory.AddFilesToMenu()
127 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
129 self.fileMenu.AppendSeparator()
130 i = self.fileMenu.Append(wx.ID_EXIT, _("Quit"))
131 self.Bind(wx.EVT_MENU, self.OnQuit, i)
132 self.menubar.Append(self.fileMenu, '&' + _("File"))
134 toolsMenu = wx.Menu()
135 #i = toolsMenu.Append(-1, 'Batch run...')
136 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
137 #self.normalModeOnlyItems.append(i)
139 if minecraftImport.hasMinecraft():
140 i = toolsMenu.Append(-1, _("Minecraft map import..."))
141 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
143 if version.isDevVersion():
144 i = toolsMenu.Append(-1, _("PID Debugger..."))
145 self.Bind(wx.EVT_MENU, self.OnPIDDebugger, i)
146 i = toolsMenu.Append(-1, _("Auto Firmware Update..."))
147 self.Bind(wx.EVT_MENU, self.OnAutoFirmwareUpdate, i)
149 #i = toolsMenu.Append(-1, _("Copy profile to clipboard"))
150 #self.Bind(wx.EVT_MENU, self.onCopyProfileClipboard,i)
152 toolsMenu.AppendSeparator()
153 self.allAtOnceItem = toolsMenu.Append(-1, _("Print all at once"), kind=wx.ITEM_RADIO)
154 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.allAtOnceItem)
155 self.oneAtATime = toolsMenu.Append(-1, _("Print one at a time"), kind=wx.ITEM_RADIO)
156 self.Bind(wx.EVT_MENU, self.onOneAtATimeSwitch, self.oneAtATime)
157 if profile.getPreference('oneAtATime') == 'True':
158 self.oneAtATime.Check(True)
160 self.allAtOnceItem.Check(True)
162 self.menubar.Append(toolsMenu, _("Tools"))
164 #Machine menu for machine configuration/tooling
165 self.machineMenu = wx.Menu()
166 self.updateMachineMenu()
168 self.menubar.Append(self.machineMenu, _("Machine"))
170 expertMenu = wx.Menu()
171 i = expertMenu.Append(-1, _("Switch to quickprint..."), kind=wx.ITEM_RADIO)
172 self.switchToQuickprintMenuItem = i
173 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
175 i = expertMenu.Append(-1, _("Switch to full settings..."), kind=wx.ITEM_RADIO)
176 self.switchToNormalMenuItem = i
177 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
178 expertMenu.AppendSeparator()
180 i = expertMenu.Append(-1, _("Open expert settings...\tCTRL+E"))
181 self.normalModeOnlyItems.append(i)
182 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
183 expertMenu.AppendSeparator()
184 self.bedLevelWizardMenuItem = expertMenu.Append(-1, _("Run bed leveling wizard..."))
185 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, self.bedLevelWizardMenuItem)
186 self.headOffsetWizardMenuItem = expertMenu.Append(-1, _("Run head offset wizard..."))
187 self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, self.headOffsetWizardMenuItem)
189 self.menubar.Append(expertMenu, _("Expert"))
192 i = helpMenu.Append(-1, _("Online documentation..."))
193 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://lulzbot.com/cura'), i)
194 i = helpMenu.Append(-1, _("Report a problem..."))
195 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/alephobjects/Cura/issues'), i)
196 #i = helpMenu.Append(-1, _("Check for update..."))
197 #self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
198 #i = helpMenu.Append(-1, _("Open YouMagine website..."))
199 #self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://www.youmagine.com/'), i)
200 i = helpMenu.Append(-1, _("About Cura..."))
201 self.Bind(wx.EVT_MENU, self.OnAbout, i)
202 self.menubar.Append(helpMenu, _("Help"))
203 self.SetMenuBar(self.menubar)
205 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
206 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
207 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
208 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
211 self.scene = sceneView.SceneView(self.rightPane)
214 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, self.scene.sceneUpdated)
215 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, self.scene.sceneUpdated)
217 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
218 self.leftSizer.Add(self.simpleSettingsPanel, 1)
219 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
220 self.leftPane.SetSizer(self.leftSizer)
222 #Main sizer, to position the preview window, buttons and tab control
223 sizer = wx.BoxSizer()
224 self.rightPane.SetSizer(sizer)
225 sizer.Add(self.scene, 1, flag=wx.EXPAND)
228 sizer = wx.BoxSizer(wx.VERTICAL)
230 sizer.Add(self.splitter, 1, wx.EXPAND)
234 self.updateProfileToAllControls()
236 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
238 self.simpleSettingsPanel.Show(False)
239 self.normalSettingsPanel.Show(False)
241 # Set default window size & position
242 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
245 #Timer set; used to check if profile is on the clipboard
246 self.timer = wx.Timer(self)
247 self.Bind(wx.EVT_TIMER, self.onTimer)
248 #self.timer.Start(1000)
249 self.lastTriedClipboard = profile.getProfileString()
251 # Restore the window position, size & state from the preferences file
253 if profile.getPreference('window_maximized') == 'True':
256 posx = int(profile.getPreference('window_pos_x'))
257 posy = int(profile.getPreference('window_pos_y'))
258 width = int(profile.getPreference('window_width'))
259 height = int(profile.getPreference('window_height'))
260 if posx > 0 or posy > 0:
261 self.SetPosition((posx,posy))
262 if width > 0 and height > 0:
263 self.SetSize((width,height))
265 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
267 self.normalSashPos = 0
269 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
270 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
272 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
274 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
276 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
278 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
279 self.SetSize((800,600))
282 self.updateSliceMode()
283 self.scene.SetFocus()
284 self.dialogframe = None
285 if Publisher is not None:
286 Publisher().subscribe(self.onPluginUpdate, "pluginupdate")
288 pluginCount = self.normalSettingsPanel.pluginPanel.GetActivePluginCount()
290 self.scene.notification.message("Warning: 1 plugin from the previous session is still active.")
293 self.scene.notification.message("Warning: %i plugins from the previous session are still active." % pluginCount)
295 def onPluginUpdate(self,msg): #receives commands from the plugin thread
296 cmd = str(msg.data).split(";")
297 if cmd[0] == "OpenPluginProgressWindow":
298 if len(cmd)==1: #no titel received
300 if len(cmd)<3: #no message text received
301 cmd.append("Plugin is executed...")
304 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)
305 self.dialogpanel = wx.Panel(self.dialogframe, -1, pos = (0,0), size = (dialogwidth,dialogheight))
306 self.dlgtext = wx.StaticText(self.dialogpanel, label = cmd[2], pos = (10,10), size = (280,40))
307 self.dlgbar = wx.Gauge(self.dialogpanel,-1, 100, pos = (10,50), size = (280,20), style = wx.GA_HORIZONTAL)
308 self.dialogframe.Show()
310 elif cmd[0] == "Progress":
312 if number <= 100 and self.dialogframe is not None:
313 self.dlgbar.SetValue(number)
315 self.dlgbar.SetValue(100)
316 elif cmd[0] == "ClosePluginProgressWindow":
317 self.dialogframe.Destroy()
318 self.dialogframe=None
320 print "Unknown Plugin update received: " + cmd[0]
322 def onTimer(self, e):
323 #Check if there is something in the clipboard
326 if not wx.TheClipboard.IsOpened():
327 if not wx.TheClipboard.Open():
329 do = wx.TextDataObject()
330 if wx.TheClipboard.GetData(do):
331 profileString = do.GetText()
332 wx.TheClipboard.Close()
334 startTag = "CURA_PROFILE_STRING:"
335 if startTag in profileString:
336 #print "Found correct syntax on clipboard"
337 profileString = profileString.replace("\n","").strip()
338 profileString = profileString[profileString.find(startTag)+len(startTag):]
339 if profileString != self.lastTriedClipboard:
341 self.lastTriedClipboard = profileString
342 profile.setProfileFromString(profileString)
343 self.scene.notification.message(_("Loaded new profile from clipboard."))
344 self.updateProfileToAllControls()
346 print "Unable to read from clipboard"
349 def updateSliceMode(self, changedMode = True):
350 isSimple = profile.getPreference('startMode') == 'Simple'
352 self.normalSettingsPanel.Show(not isSimple)
353 self.simpleSettingsPanel.Show(isSimple)
354 self.leftPane.Layout()
356 for i in self.normalModeOnlyItems:
357 i.Enable(not isSimple)
359 self.switchToQuickprintMenuItem.Check()
361 self.switchToNormalMenuItem.Check()
363 # Set splitter sash position & size
365 # Save normal mode sash (only if we changed mode from normal
368 self.normalSashPos = self.splitter.GetSashPosition()
370 # Change location of sash to width of quick mode pane
371 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
372 self.splitter.SetSashPosition(width, True)
375 self.splitter.SetSashSize(0)
377 # Only change the sash position if we changed mode from simple
379 self.splitter.SetSashPosition(self.normalSashPos, True)
381 self.splitter.SetSashSize(4)
382 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
383 if profile.getMachineSetting('machine_type').startswith('ultimaker2') or \
384 profile.getMachineSetting('machine_type').startswith('lulzbot_'):
385 self.bedLevelWizardMenuItem.Enable(False)
386 self.headOffsetWizardMenuItem.Enable(False)
387 self.oneAtATime.Enable(False)
388 self.allAtOnceItem.Check(True)
389 # Force the gantry height to 0 so we don't get a "info: print one at a time re-enabled"
390 # notification since we're disabling that option
391 profile.putMachineSetting('extruder_head_size_height', '0.0')
393 self.bedLevelWizardMenuItem.Enable(True)
394 self.headOffsetWizardMenuItem.Enable(False)
395 self.oneAtATime.Enable(True)
396 if profile.getPreference('oneAtATime') == 'True':
397 self.oneAtATime.Check(True)
399 self.allAtOnceItem.Check(True)
400 if int(profile.getMachineSetting('extruder_amount')) < 2:
401 self.headOffsetWizardMenuItem.Enable(False)
402 self.scene.updateProfileToControls()
403 self.scene._scene.pushFree()
405 def onOneAtATimeSwitch(self, e):
406 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
407 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
408 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)
409 self.scene.updateProfileToControls()
410 self.scene._scene.pushFree()
411 self.scene.sceneUpdated()
413 def OnPreferences(self, e):
414 prefDialog = preferencesDialog.preferencesDialog(self)
419 def OnMachineSettings(self, e):
420 prefDialog = preferencesDialog.machineSettingsDialog(self)
425 def OnDropFiles(self, files):
426 self.scene.loadFiles(files)
428 def OnModelMRU(self, e):
429 fileNum = e.GetId() - self.ID_MRU_MODEL1
430 path = self.modelFileHistory.GetHistoryFile(fileNum)
432 self.modelFileHistory.AddFileToHistory(path) # move up the list
433 self.config.SetPath("/ModelMRU")
434 self.modelFileHistory.Save(self.config)
437 profile.putPreference('lastFile', path)
439 self.scene.loadFiles(filelist)
441 def addToModelMRU(self, file):
442 self.modelFileHistory.AddFileToHistory(file)
443 self.config.SetPath("/ModelMRU")
444 self.modelFileHistory.Save(self.config)
447 def OnProfileMRU(self, e):
448 fileNum = e.GetId() - self.ID_MRU_PROFILE1
449 path = self.profileFileHistory.GetHistoryFile(fileNum)
451 self.profileFileHistory.AddFileToHistory(path) # move up the list
452 self.config.SetPath("/ProfileMRU")
453 self.profileFileHistory.Save(self.config)
456 profile.loadProfile(path)
457 self.updateProfileToAllControls()
459 def addToProfileMRU(self, file):
460 self.profileFileHistory.AddFileToHistory(file)
461 self.config.SetPath("/ProfileMRU")
462 self.profileFileHistory.Save(self.config)
465 def updateProfileToAllControls(self):
466 self.scene.updateProfileToControls()
467 self.normalSettingsPanel.updateProfileToControls()
468 self.simpleSettingsPanel.updateProfileToControls()
470 def reloadSettingPanels(self, changedSliceMode = False):
471 self.leftSizer.Detach(self.simpleSettingsPanel)
472 self.leftSizer.Detach(self.normalSettingsPanel)
473 self.simpleSettingsPanel.Destroy()
474 self.normalSettingsPanel.Destroy()
475 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
476 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
477 self.leftSizer.Add(self.simpleSettingsPanel, 1)
478 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
479 self.updateSliceMode(changedSliceMode)
480 self.updateProfileToAllControls()
482 def updateMachineMenu(self):
483 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
484 for item in self.machineMenu.GetMenuItems():
485 self.machineMenu.RemoveItem(item)
487 #Add a menu item for each machine configuration.
488 for n in xrange(0, profile.getMachineCount()):
489 i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
490 if n == int(profile.getPreferenceFloat('active_machine')):
492 self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
494 self.machineMenu.AppendSeparator()
495 i = self.machineMenu.Append(-1, _("Add new machine..."))
496 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
497 i = self.machineMenu.Append(-1, _("Machine settings..."))
498 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
500 #Add tools for machines.
501 self.machineMenu.AppendSeparator()
503 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
504 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
506 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
507 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
509 def OnLoadProfile(self, e):
510 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)
511 dlg.SetWildcard("ini files (*.ini)|*.ini")
512 if dlg.ShowModal() == wx.ID_OK:
513 profileFile = dlg.GetPath()
514 profile.loadProfile(profileFile)
515 self.updateProfileToAllControls()
517 # Update the Profile MRU
518 self.addToProfileMRU(profileFile)
521 def OnLoadProfileFromGcode(self, e):
522 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)
523 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
524 if dlg.ShowModal() == wx.ID_OK:
525 gcodeFile = dlg.GetPath()
526 f = open(gcodeFile, 'r')
529 if line.startswith(';CURA_PROFILE_STRING:'):
530 profile.setProfileFromString(line[line.find(':')+1:].strip())
531 if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
532 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
535 self.updateProfileToAllControls()
537 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)
540 def OnSaveProfile(self, e):
541 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
542 dlg.SetWildcard("ini files (*.ini)|*.ini")
543 if dlg.ShowModal() == wx.ID_OK:
544 profile_filename = dlg.GetPath()
545 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
546 profile_filename += '.ini'
547 profile.saveProfile(profile_filename)
550 def OnSaveDifferences(self, e):
551 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
552 dlg.SetWildcard("ini files (*.ini)|*.ini")
553 if dlg.ShowModal() == wx.ID_OK:
554 profile_filename = dlg.GetPath()
555 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
556 profile_filename += '.ini'
557 profile.saveProfileDifferenceFromDefault(profile_filename)
560 def OnResetProfile(self, e):
561 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)
562 result = dlg.ShowModal() == wx.ID_YES
565 profile.resetProfile()
566 self.updateProfileToAllControls()
568 def OnSimpleSwitch(self, e):
569 profile.putPreference('startMode', 'Simple')
570 self.updateSliceMode()
572 def OnNormalSwitch(self, e):
573 profile.putPreference('startMode', 'Normal')
574 dlg = wx.MessageDialog(self, _("Copy the settings from quickprint to your full settings?\n(This will overwrite any full setting modifications you have)"), _("Profile copy"), wx.YES_NO | wx.ICON_QUESTION)
575 result = dlg.ShowModal() == wx.ID_YES
578 profile.resetProfile()
579 for k, v in self.simpleSettingsPanel.getSettingOverrides().items():
580 if profile.isProfileSetting(k):
581 profile.putProfileSetting(k, v)
582 elif profile.isAlterationSetting(k):
583 profile.setAlterationFile(k, v)
584 self.updateProfileToAllControls()
585 self.updateSliceMode()
587 def OnDefaultMarlinFirmware(self, e):
588 firmwareInstall.InstallFirmware(self)
590 def OnCustomFirmware(self, e):
591 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
592 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)
593 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
594 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
595 if dlg.ShowModal() == wx.ID_OK:
596 filename = dlg.GetPath()
598 if not(os.path.exists(filename)):
600 #For some reason my Ubuntu 10.10 crashes here.
601 firmwareInstall.InstallFirmware(self, filename)
603 def OnAddNewMachine(self, e):
605 wasSimple = profile.getPreference('startMode') == 'Simple'
606 configWizard.ConfigWizard(True)
607 isSimple = profile.getPreference('startMode') == 'Simple'
609 self.reloadSettingPanels(isSimple != wasSimple)
610 self.updateMachineMenu()
612 def OnSelectMachine(self, index):
613 profile.setActiveMachine(index)
614 self.reloadSettingPanels(False)
616 def OnBedLevelWizard(self, e):
617 configWizard.bedLevelWizard()
619 def OnHeadOffsetWizard(self, e):
620 configWizard.headOffsetWizard()
622 def OnExpertOpen(self, e):
623 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
627 def OnMinecraftImport(self, e):
628 mi = minecraftImport.minecraftImportWindow(self)
632 def OnPIDDebugger(self, e):
633 debugger = pidDebugger.debuggerWindow(self)
637 def OnAutoFirmwareUpdate(self, e):
638 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
639 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
640 if dlg.ShowModal() == wx.ID_OK:
641 filename = dlg.GetPath()
643 if not(os.path.exists(filename)):
645 #For some reason my Ubuntu 10.10 crashes here.
646 installer = firmwareInstall.AutoUpdateFirmware(self, filename)
648 def onCopyProfileClipboard(self, e):
650 if not wx.TheClipboard.IsOpened():
651 wx.TheClipboard.Open()
652 clipData = wx.TextDataObject()
653 self.lastTriedClipboard = profile.getProfileString()
654 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
655 clipData.SetText(profileString)
656 wx.TheClipboard.SetData(clipData)
657 wx.TheClipboard.Close()
659 print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
661 def OnCheckForUpdate(self, e):
662 newVersion = version.checkForNewerVersion()
663 if newVersion is not None:
664 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:
665 webbrowser.open(newVersion)
667 wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
669 def OnAbout(self, e):
670 aboutBox = aboutWindow.aboutWindow(self)
675 def OnClose(self, e):
676 profile.saveProfile(profile.getDefaultProfilePath(), True)
678 # Save the window position, size & state from the preferences file
679 profile.putPreference('window_maximized', self.IsMaximized())
680 if not self.IsMaximized() and not self.IsIconized():
681 (posx, posy) = self.GetPosition()
682 profile.putPreference('window_pos_x', posx)
683 profile.putPreference('window_pos_y', posy)
684 (width, height) = self.GetSize()
685 profile.putPreference('window_width', width)
686 profile.putPreference('window_height', height)
688 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
689 isSimple = profile.getPreference('startMode') == 'Simple'
691 self.normalSashPos = self.splitter.GetSashPosition()
692 profile.putPreference('window_normal_sash', self.normalSashPos)
694 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
696 self.scene.OnPaint = lambda e : e
703 class normalSettingsPanel(configBase.configPanelBase):
704 "Main user interface window"
705 def __init__(self, parent, callback = None):
706 super(normalSettingsPanel, self).__init__(parent, callback)
709 self.nb = wx.Notebook(self)
710 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
711 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
713 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, _('Basic'))
714 self._addSettingsToPanels('basic', left, right)
715 self.SizeLabelWidths(left, right)
717 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, _('Advanced'))
718 self._addSettingsToPanels('advanced', left, right)
719 self.SizeLabelWidths(left, right)
722 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
723 self.nb.AddPage(self.pluginPanel, _("Plugins"))
726 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
727 self.alterationPanel = None
729 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
730 self.nb.AddPage(self.alterationPanel, _("Start/End-GCode"))
732 self.Bind(wx.EVT_SIZE, self.OnSize)
734 self.nb.SetSize(self.GetSize())
735 self.UpdateSize(self.printPanel)
736 self.UpdateSize(self.advancedPanel)
738 def _addSettingsToPanels(self, category, left, right):
739 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
743 for title in profile.getSubCategoriesFor(category):
744 n += 1 + len(profile.getSettingsForCategory(category, title))
747 configBase.TitleRow(p, _(title))
748 for s in profile.getSettingsForCategory(category, title):
749 configBase.SettingRow(p, s.getName())
751 def SizeLabelWidths(self, left, right):
752 leftWidth = self.getLabelColumnWidth(left)
753 rightWidth = self.getLabelColumnWidth(right)
754 maxWidth = max(leftWidth, rightWidth)
755 self.setLabelColumnWidth(left, maxWidth)
756 self.setLabelColumnWidth(right, maxWidth)
759 # Make the size of the Notebook control the same size as this control
760 self.nb.SetSize(self.GetSize())
762 # Propegate the OnSize() event (just in case)
765 # Perform out resize magic
766 self.UpdateSize(self.printPanel)
767 self.UpdateSize(self.advancedPanel)
769 def UpdateSize(self, configPanel):
770 sizer = configPanel.GetSizer()
774 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
777 # if width(col1) > (best_width(col1) + best_width(col1)):
778 # switch to horizontal
781 col1 = configPanel.leftPanel
782 colSize1 = col1.GetSize()
783 colBestSize1 = col1.GetBestSize()
784 col2 = configPanel.rightPanel
785 colSize2 = col2.GetSize()
786 colBestSize2 = col2.GetBestSize()
788 orientation = sizer.GetOrientation()
790 if orientation == wx.HORIZONTAL:
791 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
793 sizer = wx.BoxSizer(wx.VERTICAL)
794 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
795 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
796 configPanel.SetSizer(sizer)
802 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
804 sizer = wx.BoxSizer(wx.HORIZONTAL)
805 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
806 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
807 configPanel.SetSizer(sizer)
813 def updateProfileToControls(self):
814 super(normalSettingsPanel, self).updateProfileToControls()
815 if self.alterationPanel is not None:
816 self.alterationPanel.updateProfileToControls()
817 self.pluginPanel.updateProfileToControls()