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 self.oneAtATime.Enable(True)
391 if profile.getPreference('oneAtATime') == 'True':
392 self.oneAtATime.Check(True)
394 self.allAtOnceItem.Check(True)
395 if int(profile.getMachineSetting('extruder_amount')) < 2:
396 self.headOffsetWizardMenuItem.Enable(False)
397 self.scene.updateProfileToControls()
398 self.scene._scene.pushFree()
400 def onOneAtATimeSwitch(self, e):
401 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
402 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
403 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)
404 self.scene.updateProfileToControls()
405 self.scene._scene.pushFree()
406 self.scene.sceneUpdated()
408 def OnPreferences(self, e):
409 prefDialog = preferencesDialog.preferencesDialog(self)
414 def OnMachineSettings(self, e):
415 prefDialog = preferencesDialog.machineSettingsDialog(self)
420 def OnDropFiles(self, files):
421 self.scene.loadFiles(files)
423 def OnModelMRU(self, e):
424 fileNum = e.GetId() - self.ID_MRU_MODEL1
425 path = self.modelFileHistory.GetHistoryFile(fileNum)
427 self.modelFileHistory.AddFileToHistory(path) # move up the list
428 self.config.SetPath("/ModelMRU")
429 self.modelFileHistory.Save(self.config)
432 profile.putPreference('lastFile', path)
434 self.scene.loadFiles(filelist)
436 def addToModelMRU(self, file):
437 self.modelFileHistory.AddFileToHistory(file)
438 self.config.SetPath("/ModelMRU")
439 self.modelFileHistory.Save(self.config)
442 def OnProfileMRU(self, e):
443 fileNum = e.GetId() - self.ID_MRU_PROFILE1
444 path = self.profileFileHistory.GetHistoryFile(fileNum)
446 self.profileFileHistory.AddFileToHistory(path) # move up the list
447 self.config.SetPath("/ProfileMRU")
448 self.profileFileHistory.Save(self.config)
451 profile.loadProfile(path)
452 self.updateProfileToAllControls()
454 def addToProfileMRU(self, file):
455 self.profileFileHistory.AddFileToHistory(file)
456 self.config.SetPath("/ProfileMRU")
457 self.profileFileHistory.Save(self.config)
460 def updateProfileToAllControls(self):
461 self.scene.updateProfileToControls()
462 self.normalSettingsPanel.updateProfileToControls()
463 self.simpleSettingsPanel.updateProfileToControls()
465 def reloadSettingPanels(self, changedSliceMode = False):
466 self.leftSizer.Detach(self.simpleSettingsPanel)
467 self.leftSizer.Detach(self.normalSettingsPanel)
468 self.simpleSettingsPanel.Destroy()
469 self.normalSettingsPanel.Destroy()
470 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
471 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
472 self.leftSizer.Add(self.simpleSettingsPanel, 1)
473 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
474 self.updateSliceMode(changedSliceMode)
475 self.updateProfileToAllControls()
477 def updateMachineMenu(self):
478 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
479 for item in self.machineMenu.GetMenuItems():
480 self.machineMenu.RemoveItem(item)
482 #Add a menu item for each machine configuration.
483 for n in xrange(0, profile.getMachineCount()):
484 i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
485 if n == int(profile.getPreferenceFloat('active_machine')):
487 self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
489 self.machineMenu.AppendSeparator()
490 i = self.machineMenu.Append(-1, _("Add new machine..."))
491 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
492 i = self.machineMenu.Append(-1, _("Machine settings..."))
493 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
495 #Add tools for machines.
496 self.machineMenu.AppendSeparator()
498 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
499 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
501 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
502 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
504 def OnLoadProfile(self, e):
505 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)
506 dlg.SetWildcard("ini files (*.ini)|*.ini")
507 if dlg.ShowModal() == wx.ID_OK:
508 profileFile = dlg.GetPath()
509 profile.loadProfile(profileFile)
510 self.updateProfileToAllControls()
512 # Update the Profile MRU
513 self.addToProfileMRU(profileFile)
516 def OnLoadProfileFromGcode(self, e):
517 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)
518 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
519 if dlg.ShowModal() == wx.ID_OK:
520 gcodeFile = dlg.GetPath()
521 f = open(gcodeFile, 'r')
524 if line.startswith(';CURA_PROFILE_STRING:'):
525 profile.setProfileFromString(line[line.find(':')+1:].strip())
526 if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
527 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
530 self.updateProfileToAllControls()
532 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)
535 def OnSaveProfile(self, e):
536 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
537 dlg.SetWildcard("ini files (*.ini)|*.ini")
538 if dlg.ShowModal() == wx.ID_OK:
539 profile_filename = dlg.GetPath()
540 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
541 profile_filename += '.ini'
542 profile.saveProfile(profile_filename)
545 def OnSaveDifferences(self, e):
546 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
547 dlg.SetWildcard("ini files (*.ini)|*.ini")
548 if dlg.ShowModal() == wx.ID_OK:
549 profile_filename = dlg.GetPath()
550 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
551 profile_filename += '.ini'
552 profile.saveProfileDifferenceFromDefault(profile_filename)
555 def OnResetProfile(self, e):
556 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)
557 result = dlg.ShowModal() == wx.ID_YES
560 profile.resetProfile()
561 self.updateProfileToAllControls()
563 def OnSimpleSwitch(self, e):
564 profile.putPreference('startMode', 'Simple')
565 self.updateSliceMode()
567 def OnNormalSwitch(self, e):
568 profile.putPreference('startMode', 'Normal')
569 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)
570 result = dlg.ShowModal() == wx.ID_YES
573 profile.resetProfile()
574 for k, v in self.simpleSettingsPanel.getSettingOverrides().items():
575 if profile.isProfileSetting(k):
576 profile.putProfileSetting(k, v)
577 elif profile.isAlterationSetting(k):
578 profile.setAlterationFile(k, v)
579 self.updateProfileToAllControls()
580 self.updateSliceMode()
582 def OnDefaultMarlinFirmware(self, e):
583 firmwareInstall.InstallFirmware(self)
585 def OnCustomFirmware(self, e):
586 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
587 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)
588 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
589 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
590 if dlg.ShowModal() == wx.ID_OK:
591 filename = dlg.GetPath()
593 if not(os.path.exists(filename)):
595 #For some reason my Ubuntu 10.10 crashes here.
596 firmwareInstall.InstallFirmware(self, filename)
598 def OnAddNewMachine(self, e):
600 wasSimple = profile.getPreference('startMode') == 'Simple'
601 configWizard.ConfigWizard(True)
602 isSimple = profile.getPreference('startMode') == 'Simple'
604 self.reloadSettingPanels(isSimple != wasSimple)
605 self.updateMachineMenu()
607 def OnSelectMachine(self, index):
608 profile.setActiveMachine(index)
609 self.reloadSettingPanels(False)
611 def OnBedLevelWizard(self, e):
612 configWizard.bedLevelWizard()
614 def OnHeadOffsetWizard(self, e):
615 configWizard.headOffsetWizard()
617 def OnExpertOpen(self, e):
618 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
622 def OnMinecraftImport(self, e):
623 mi = minecraftImport.minecraftImportWindow(self)
627 def OnPIDDebugger(self, e):
628 debugger = pidDebugger.debuggerWindow(self)
632 def OnAutoFirmwareUpdate(self, e):
633 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
634 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
635 if dlg.ShowModal() == wx.ID_OK:
636 filename = dlg.GetPath()
638 if not(os.path.exists(filename)):
640 #For some reason my Ubuntu 10.10 crashes here.
641 installer = firmwareInstall.AutoUpdateFirmware(self, filename)
643 def onCopyProfileClipboard(self, e):
645 if not wx.TheClipboard.IsOpened():
646 wx.TheClipboard.Open()
647 clipData = wx.TextDataObject()
648 self.lastTriedClipboard = profile.getProfileString()
649 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
650 clipData.SetText(profileString)
651 wx.TheClipboard.SetData(clipData)
652 wx.TheClipboard.Close()
654 print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
656 def OnCheckForUpdate(self, e):
657 newVersion = version.checkForNewerVersion()
658 if newVersion is not None:
659 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:
660 webbrowser.open(newVersion)
662 wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
664 def OnAbout(self, e):
665 aboutBox = aboutWindow.aboutWindow(self)
670 def OnClose(self, e):
671 profile.saveProfile(profile.getDefaultProfilePath(), True)
673 # Save the window position, size & state from the preferences file
674 profile.putPreference('window_maximized', self.IsMaximized())
675 if not self.IsMaximized() and not self.IsIconized():
676 (posx, posy) = self.GetPosition()
677 profile.putPreference('window_pos_x', posx)
678 profile.putPreference('window_pos_y', posy)
679 (width, height) = self.GetSize()
680 profile.putPreference('window_width', width)
681 profile.putPreference('window_height', height)
683 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
684 isSimple = profile.getPreference('startMode') == 'Simple'
686 self.normalSashPos = self.splitter.GetSashPosition()
687 profile.putPreference('window_normal_sash', self.normalSashPos)
689 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
691 self.scene.OnPaint = lambda e : e
698 class normalSettingsPanel(configBase.configPanelBase):
699 "Main user interface window"
700 def __init__(self, parent, callback = None):
701 super(normalSettingsPanel, self).__init__(parent, callback)
704 self.nb = wx.Notebook(self)
705 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
706 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
708 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, _('Basic'))
709 self._addSettingsToPanels('basic', left, right)
710 self.SizeLabelWidths(left, right)
712 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, _('Advanced'))
713 self._addSettingsToPanels('advanced', left, right)
714 self.SizeLabelWidths(left, right)
717 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
718 self.nb.AddPage(self.pluginPanel, _("Plugins"))
721 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
722 self.alterationPanel = None
724 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
725 self.nb.AddPage(self.alterationPanel, _("Start/End-GCode"))
727 self.Bind(wx.EVT_SIZE, self.OnSize)
729 self.nb.SetSize(self.GetSize())
730 self.UpdateSize(self.printPanel)
731 self.UpdateSize(self.advancedPanel)
733 def _addSettingsToPanels(self, category, left, right):
734 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
738 for title in profile.getSubCategoriesFor(category):
739 n += 1 + len(profile.getSettingsForCategory(category, title))
742 configBase.TitleRow(p, _(title))
743 for s in profile.getSettingsForCategory(category, title):
744 configBase.SettingRow(p, s.getName())
746 def SizeLabelWidths(self, left, right):
747 leftWidth = self.getLabelColumnWidth(left)
748 rightWidth = self.getLabelColumnWidth(right)
749 maxWidth = max(leftWidth, rightWidth)
750 self.setLabelColumnWidth(left, maxWidth)
751 self.setLabelColumnWidth(right, maxWidth)
754 # Make the size of the Notebook control the same size as this control
755 self.nb.SetSize(self.GetSize())
757 # Propegate the OnSize() event (just in case)
760 # Perform out resize magic
761 self.UpdateSize(self.printPanel)
762 self.UpdateSize(self.advancedPanel)
764 def UpdateSize(self, configPanel):
765 sizer = configPanel.GetSizer()
769 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
772 # if width(col1) > (best_width(col1) + best_width(col1)):
773 # switch to horizontal
776 col1 = configPanel.leftPanel
777 colSize1 = col1.GetSize()
778 colBestSize1 = col1.GetBestSize()
779 col2 = configPanel.rightPanel
780 colSize2 = col2.GetSize()
781 colBestSize2 = col2.GetBestSize()
783 orientation = sizer.GetOrientation()
785 if orientation == wx.HORIZONTAL:
786 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
788 sizer = wx.BoxSizer(wx.VERTICAL)
789 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
790 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
791 configPanel.SetSizer(sizer)
797 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
799 sizer = wx.BoxSizer(wx.HORIZONTAL)
800 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
801 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
802 configPanel.SetSizer(sizer)
808 def updateProfileToControls(self):
809 super(normalSettingsPanel, self).updateProfileToControls()
810 if self.alterationPanel is not None:
811 self.alterationPanel.updateProfileToControls()
812 self.pluginPanel.updateProfileToControls()