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)
388 self.bedLevelWizardMenuItem.Enable(True)
389 self.headOffsetWizardMenuItem.Enable(False)
390 if int(profile.getMachineSetting('extruder_amount')) < 2:
391 self.headOffsetWizardMenuItem.Enable(False)
392 self.scene.updateProfileToControls()
393 self.scene._scene.pushFree()
395 def onOneAtATimeSwitch(self, e):
396 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
397 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
398 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)
399 self.scene.updateProfileToControls()
400 self.scene._scene.pushFree()
401 self.scene.sceneUpdated()
403 def OnPreferences(self, e):
404 prefDialog = preferencesDialog.preferencesDialog(self)
409 def OnMachineSettings(self, e):
410 prefDialog = preferencesDialog.machineSettingsDialog(self)
415 def OnDropFiles(self, files):
416 self.scene.loadFiles(files)
418 def OnModelMRU(self, e):
419 fileNum = e.GetId() - self.ID_MRU_MODEL1
420 path = self.modelFileHistory.GetHistoryFile(fileNum)
422 self.modelFileHistory.AddFileToHistory(path) # move up the list
423 self.config.SetPath("/ModelMRU")
424 self.modelFileHistory.Save(self.config)
427 profile.putPreference('lastFile', path)
429 self.scene.loadFiles(filelist)
431 def addToModelMRU(self, file):
432 self.modelFileHistory.AddFileToHistory(file)
433 self.config.SetPath("/ModelMRU")
434 self.modelFileHistory.Save(self.config)
437 def OnProfileMRU(self, e):
438 fileNum = e.GetId() - self.ID_MRU_PROFILE1
439 path = self.profileFileHistory.GetHistoryFile(fileNum)
441 self.profileFileHistory.AddFileToHistory(path) # move up the list
442 self.config.SetPath("/ProfileMRU")
443 self.profileFileHistory.Save(self.config)
446 profile.loadProfile(path)
447 self.updateProfileToAllControls()
449 def addToProfileMRU(self, file):
450 self.profileFileHistory.AddFileToHistory(file)
451 self.config.SetPath("/ProfileMRU")
452 self.profileFileHistory.Save(self.config)
455 def updateProfileToAllControls(self):
456 self.scene.updateProfileToControls()
457 self.normalSettingsPanel.updateProfileToControls()
458 self.simpleSettingsPanel.updateProfileToControls()
460 def reloadSettingPanels(self, changedSliceMode = False):
461 self.leftSizer.Detach(self.simpleSettingsPanel)
462 self.leftSizer.Detach(self.normalSettingsPanel)
463 self.simpleSettingsPanel.Destroy()
464 self.normalSettingsPanel.Destroy()
465 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
466 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
467 self.leftSizer.Add(self.simpleSettingsPanel, 1)
468 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
469 self.updateSliceMode(changedSliceMode)
470 self.updateProfileToAllControls()
472 def updateMachineMenu(self):
473 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
474 for item in self.machineMenu.GetMenuItems():
475 self.machineMenu.RemoveItem(item)
477 #Add a menu item for each machine configuration.
478 for n in xrange(0, profile.getMachineCount()):
479 i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
480 if n == int(profile.getPreferenceFloat('active_machine')):
482 self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
484 self.machineMenu.AppendSeparator()
485 i = self.machineMenu.Append(-1, _("Add new machine..."))
486 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
487 i = self.machineMenu.Append(-1, _("Machine settings..."))
488 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
490 #Add tools for machines.
491 self.machineMenu.AppendSeparator()
493 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
494 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
496 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
497 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
499 def OnLoadProfile(self, e):
500 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)
501 dlg.SetWildcard("ini files (*.ini)|*.ini")
502 if dlg.ShowModal() == wx.ID_OK:
503 profileFile = dlg.GetPath()
504 profile.loadProfile(profileFile)
505 self.updateProfileToAllControls()
507 # Update the Profile MRU
508 self.addToProfileMRU(profileFile)
511 def OnLoadProfileFromGcode(self, e):
512 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)
513 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
514 if dlg.ShowModal() == wx.ID_OK:
515 gcodeFile = dlg.GetPath()
516 f = open(gcodeFile, 'r')
519 if line.startswith(';CURA_PROFILE_STRING:'):
520 profile.setProfileFromString(line[line.find(':')+1:].strip())
521 if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
522 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
525 self.updateProfileToAllControls()
527 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)
530 def OnSaveProfile(self, e):
531 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
532 dlg.SetWildcard("ini files (*.ini)|*.ini")
533 if dlg.ShowModal() == wx.ID_OK:
534 profile_filename = dlg.GetPath()
535 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
536 profile_filename += '.ini'
537 profile.saveProfile(profile_filename)
540 def OnSaveDifferences(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.saveProfileDifferenceFromDefault(profile_filename)
550 def OnResetProfile(self, e):
551 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)
552 result = dlg.ShowModal() == wx.ID_YES
555 profile.resetProfile()
556 self.updateProfileToAllControls()
558 def OnSimpleSwitch(self, e):
559 profile.putPreference('startMode', 'Simple')
560 self.updateSliceMode()
562 def OnNormalSwitch(self, e):
563 profile.putPreference('startMode', 'Normal')
564 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)
565 result = dlg.ShowModal() == wx.ID_YES
568 profile.resetProfile()
569 for k, v in self.simpleSettingsPanel.getSettingOverrides().items():
570 profile.putProfileSetting(k, v)
571 self.updateProfileToAllControls()
572 self.updateSliceMode()
574 def OnDefaultMarlinFirmware(self, e):
575 firmwareInstall.InstallFirmware(self)
577 def OnCustomFirmware(self, e):
578 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
579 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)
580 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
581 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
582 if dlg.ShowModal() == wx.ID_OK:
583 filename = dlg.GetPath()
585 if not(os.path.exists(filename)):
587 #For some reason my Ubuntu 10.10 crashes here.
588 firmwareInstall.InstallFirmware(self, filename)
590 def OnAddNewMachine(self, e):
592 wasSimple = profile.getPreference('startMode') == 'Simple'
593 configWizard.ConfigWizard(True)
594 isSimple = profile.getPreference('startMode') == 'Simple'
596 self.reloadSettingPanels(isSimple != wasSimple)
597 self.updateMachineMenu()
599 def OnSelectMachine(self, index):
600 profile.setActiveMachine(index)
601 self.reloadSettingPanels(False)
603 def OnBedLevelWizard(self, e):
604 configWizard.bedLevelWizard()
606 def OnHeadOffsetWizard(self, e):
607 configWizard.headOffsetWizard()
609 def OnExpertOpen(self, e):
610 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
614 def OnMinecraftImport(self, e):
615 mi = minecraftImport.minecraftImportWindow(self)
619 def OnPIDDebugger(self, e):
620 debugger = pidDebugger.debuggerWindow(self)
624 def OnAutoFirmwareUpdate(self, e):
625 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
626 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
627 if dlg.ShowModal() == wx.ID_OK:
628 filename = dlg.GetPath()
630 if not(os.path.exists(filename)):
632 #For some reason my Ubuntu 10.10 crashes here.
633 installer = firmwareInstall.AutoUpdateFirmware(self, filename)
635 def onCopyProfileClipboard(self, e):
637 if not wx.TheClipboard.IsOpened():
638 wx.TheClipboard.Open()
639 clipData = wx.TextDataObject()
640 self.lastTriedClipboard = profile.getProfileString()
641 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
642 clipData.SetText(profileString)
643 wx.TheClipboard.SetData(clipData)
644 wx.TheClipboard.Close()
646 print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
648 def OnCheckForUpdate(self, e):
649 newVersion = version.checkForNewerVersion()
650 if newVersion is not None:
651 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:
652 webbrowser.open(newVersion)
654 wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
656 def OnAbout(self, e):
657 aboutBox = aboutWindow.aboutWindow(self)
662 def OnClose(self, e):
663 profile.saveProfile(profile.getDefaultProfilePath(), True)
665 # Save the window position, size & state from the preferences file
666 profile.putPreference('window_maximized', self.IsMaximized())
667 if not self.IsMaximized() and not self.IsIconized():
668 (posx, posy) = self.GetPosition()
669 profile.putPreference('window_pos_x', posx)
670 profile.putPreference('window_pos_y', posy)
671 (width, height) = self.GetSize()
672 profile.putPreference('window_width', width)
673 profile.putPreference('window_height', height)
675 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
676 isSimple = profile.getPreference('startMode') == 'Simple'
678 self.normalSashPos = self.splitter.GetSashPosition()
679 profile.putPreference('window_normal_sash', self.normalSashPos)
681 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
683 self.scene.OnPaint = lambda e : e
690 class normalSettingsPanel(configBase.configPanelBase):
691 "Main user interface window"
692 def __init__(self, parent, callback = None):
693 super(normalSettingsPanel, self).__init__(parent, callback)
696 self.nb = wx.Notebook(self)
697 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
698 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
700 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, _('Basic'))
701 self._addSettingsToPanels('basic', left, right)
702 self.SizeLabelWidths(left, right)
704 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, _('Advanced'))
705 self._addSettingsToPanels('advanced', left, right)
706 self.SizeLabelWidths(left, right)
709 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
710 self.nb.AddPage(self.pluginPanel, _("Plugins"))
713 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
714 self.alterationPanel = None
716 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
717 self.nb.AddPage(self.alterationPanel, _("Start/End-GCode"))
719 self.Bind(wx.EVT_SIZE, self.OnSize)
721 self.nb.SetSize(self.GetSize())
722 self.UpdateSize(self.printPanel)
723 self.UpdateSize(self.advancedPanel)
725 def _addSettingsToPanels(self, category, left, right):
726 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
730 for title in profile.getSubCategoriesFor(category):
731 n += 1 + len(profile.getSettingsForCategory(category, title))
734 configBase.TitleRow(p, _(title))
735 for s in profile.getSettingsForCategory(category, title):
736 configBase.SettingRow(p, s.getName())
738 def SizeLabelWidths(self, left, right):
739 leftWidth = self.getLabelColumnWidth(left)
740 rightWidth = self.getLabelColumnWidth(right)
741 maxWidth = max(leftWidth, rightWidth)
742 self.setLabelColumnWidth(left, maxWidth)
743 self.setLabelColumnWidth(right, maxWidth)
746 # Make the size of the Notebook control the same size as this control
747 self.nb.SetSize(self.GetSize())
749 # Propegate the OnSize() event (just in case)
752 # Perform out resize magic
753 self.UpdateSize(self.printPanel)
754 self.UpdateSize(self.advancedPanel)
756 def UpdateSize(self, configPanel):
757 sizer = configPanel.GetSizer()
761 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
764 # if width(col1) > (best_width(col1) + best_width(col1)):
765 # switch to horizontal
768 col1 = configPanel.leftPanel
769 colSize1 = col1.GetSize()
770 colBestSize1 = col1.GetBestSize()
771 col2 = configPanel.rightPanel
772 colSize2 = col2.GetSize()
773 colBestSize2 = col2.GetBestSize()
775 orientation = sizer.GetOrientation()
777 if orientation == wx.HORIZONTAL:
778 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
780 sizer = wx.BoxSizer(wx.VERTICAL)
781 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
782 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
783 configPanel.SetSizer(sizer)
789 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
791 sizer = wx.BoxSizer(wx.HORIZONTAL)
792 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
793 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
794 configPanel.SetSizer(sizer)
800 def updateProfileToControls(self):
801 super(normalSettingsPanel, self).updateProfileToControls()
802 if self.alterationPanel is not None:
803 self.alterationPanel.updateProfileToControls()
804 self.pluginPanel.updateProfileToControls()