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://code.alephobjects.com/maniphest/task/create/?projects=Cura'), 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.splitter.SetMinimumPaneSize(100)
207 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
208 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
209 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
212 self.scene = sceneView.SceneView(self.rightPane)
215 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, self.simpleModeUpdated)
216 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, self.scene.sceneUpdated)
218 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
219 self.leftSizer.Add(self.simpleSettingsPanel, 1)
220 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
221 self.leftPane.SetSizer(self.leftSizer)
223 #Main sizer, to position the preview window, buttons and tab control
224 sizer = wx.BoxSizer()
225 self.rightPane.SetSizer(sizer)
226 sizer.Add(self.scene, 1, flag=wx.EXPAND)
229 sizer = wx.BoxSizer(wx.VERTICAL)
231 sizer.Add(self.splitter, 1, wx.EXPAND)
235 self.updateProfileToAllControls()
237 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
239 self.simpleSettingsPanel.Show(False)
240 self.normalSettingsPanel.Show(False)
242 # Set default window size & position
243 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
246 #Timer set; used to check if profile is on the clipboard
247 self.timer = wx.Timer(self)
248 self.Bind(wx.EVT_TIMER, self.onTimer)
249 #self.timer.Start(1000)
250 self.lastTriedClipboard = profile.getProfileString()
252 # Restore the window position, size & state from the preferences file
254 if profile.getPreference('window_maximized') == 'True':
257 posx = int(profile.getPreference('window_pos_x'))
258 posy = int(profile.getPreference('window_pos_y'))
259 width = int(profile.getPreference('window_width'))
260 height = int(profile.getPreference('window_height'))
261 if posx > 0 or posy > 0:
262 self.SetPosition((posx,posy))
263 if width > 0 and height > 0:
264 self.SetSize((width,height))
266 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
268 self.normalSashPos = 0
270 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
271 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
273 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
275 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
277 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
279 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
280 self.SetSize((800,600))
283 self.updateSliceMode(False)
284 self.scene.SetFocus()
285 self.dialogframe = None
286 if Publisher is not None:
287 Publisher().subscribe(self.onPluginUpdate, "pluginupdate")
289 pluginCount = self.normalSettingsPanel.pluginPanel.GetActivePluginCount()
291 self.scene.notification.message("Warning: 1 plugin from the previous session is still active.")
294 self.scene.notification.message("Warning: %i plugins from the previous session are still active." % pluginCount)
296 def onPluginUpdate(self,msg): #receives commands from the plugin thread
297 cmd = str(msg.data).split(";")
298 if cmd[0] == "OpenPluginProgressWindow":
299 if len(cmd)==1: #no titel received
301 if len(cmd)<3: #no message text received
302 cmd.append("Plugin is executed...")
305 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)
306 self.dialogpanel = wx.Panel(self.dialogframe, -1, pos = (0,0), size = (dialogwidth,dialogheight))
307 self.dlgtext = wx.StaticText(self.dialogpanel, label = cmd[2], pos = (10,10), size = (280,40))
308 self.dlgbar = wx.Gauge(self.dialogpanel,-1, 100, pos = (10,50), size = (280,20), style = wx.GA_HORIZONTAL)
309 self.dialogframe.Show()
311 elif cmd[0] == "Progress":
313 if number <= 100 and self.dialogframe is not None:
314 self.dlgbar.SetValue(number)
316 self.dlgbar.SetValue(100)
317 elif cmd[0] == "ClosePluginProgressWindow":
318 self.dialogframe.Destroy()
319 self.dialogframe=None
321 print "Unknown Plugin update received: " + cmd[0]
323 def onTimer(self, e):
324 #Check if there is something in the clipboard
327 if not wx.TheClipboard.IsOpened():
328 if not wx.TheClipboard.Open():
330 do = wx.TextDataObject()
331 if wx.TheClipboard.GetData(do):
332 profileString = do.GetText()
333 wx.TheClipboard.Close()
335 startTag = "CURA_PROFILE_STRING:"
336 if startTag in profileString:
337 #print "Found correct syntax on clipboard"
338 profileString = profileString.replace("\n","").strip()
339 profileString = profileString[profileString.find(startTag)+len(startTag):]
340 if profileString != self.lastTriedClipboard:
342 self.lastTriedClipboard = profileString
343 profile.setProfileFromString(profileString)
344 self.scene.notification.message(_("Loaded new profile from clipboard."))
345 self.updateProfileToAllControls()
347 print "Unable to read from clipboard"
350 def simpleModeUpdated(self):
351 self.leftPane.Layout()
352 if profile.getPreference('startMode') == 'Simple':
353 # Change location of sash to width of quick mode pane
354 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
355 self.splitter.SetSashPosition(width, True)
356 self.scene.sceneUpdated()
358 def updateSliceMode(self, changedMode = True):
359 isSimple = profile.getPreference('startMode') == 'Simple'
361 self.normalSettingsPanel.Show(not isSimple)
362 self.simpleSettingsPanel.Show(isSimple)
363 self.leftPane.Layout()
365 for i in self.normalModeOnlyItems:
366 i.Enable(not isSimple)
368 self.switchToQuickprintMenuItem.Check()
370 self.switchToNormalMenuItem.Check()
372 # Set splitter sash position & size
374 # Save normal mode sash (only if we changed mode from normal
377 self.normalSashPos = self.splitter.GetSashPosition()
379 # Change location of sash to width of quick mode pane
380 self.simpleSettingsPanel.Layout()
381 self.simpleSettingsPanel.Fit()
382 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
384 self.splitter.SetSashPosition(width, True)
386 self.splitter.SizeWindows()
389 self.splitter.SetSashSize(0)
391 # Only change the sash position if we changed mode from simple
393 self.splitter.SetSashPosition(self.normalSashPos, True)
396 self.splitter.SetSashSize(4)
397 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
398 if profile.getMachineSetting('machine_type').startswith('ultimaker2') or \
399 profile.getMachineSetting('machine_type').startswith('lulzbot_'):
400 self.bedLevelWizardMenuItem.Enable(False)
401 self.headOffsetWizardMenuItem.Enable(False)
403 self.bedLevelWizardMenuItem.Enable(True)
404 self.headOffsetWizardMenuItem.Enable(False)
405 self.oneAtATime.Enable(True)
406 if profile.getPreference('oneAtATime') == 'True':
407 self.oneAtATime.Check(True)
409 self.allAtOnceItem.Check(True)
410 if int(profile.getMachineSetting('extruder_amount')) < 2:
411 self.headOffsetWizardMenuItem.Enable(False)
412 self.scene.updateProfileToControls()
413 self.scene._scene.pushFree()
415 def onOneAtATimeSwitch(self, e):
416 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
417 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
418 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)
419 self.scene.updateProfileToControls()
420 self.scene._scene.pushFree()
421 self.scene.sceneUpdated()
423 def OnPreferences(self, e):
424 prefDialog = preferencesDialog.preferencesDialog(self)
429 def OnMachineSettings(self, e):
430 prefDialog = preferencesDialog.machineSettingsDialog(self)
435 def OnDropFiles(self, files):
436 self.scene.loadFiles(files)
438 def OnModelMRU(self, e):
439 fileNum = e.GetId() - self.ID_MRU_MODEL1
440 path = self.modelFileHistory.GetHistoryFile(fileNum)
442 self.modelFileHistory.AddFileToHistory(path) # move up the list
443 self.config.SetPath("/ModelMRU")
444 self.modelFileHistory.Save(self.config)
447 profile.putPreference('lastFile', path)
449 self.scene.loadFiles(filelist)
451 def addToModelMRU(self, file):
452 self.modelFileHistory.AddFileToHistory(file)
453 self.config.SetPath("/ModelMRU")
454 self.modelFileHistory.Save(self.config)
457 def OnProfileMRU(self, e):
458 fileNum = e.GetId() - self.ID_MRU_PROFILE1
459 path = self.profileFileHistory.GetHistoryFile(fileNum)
461 self.profileFileHistory.AddFileToHistory(path) # move up the list
462 self.config.SetPath("/ProfileMRU")
463 self.profileFileHistory.Save(self.config)
466 profile.loadProfile(path)
467 self.updateProfileToAllControls()
469 def addToProfileMRU(self, file):
470 self.profileFileHistory.AddFileToHistory(file)
471 self.config.SetPath("/ProfileMRU")
472 self.profileFileHistory.Save(self.config)
475 def updateProfileToAllControls(self):
476 self.scene.updateProfileToControls()
477 self.normalSettingsPanel.updateProfileToControls()
478 self.simpleSettingsPanel.updateProfileToControls()
480 def reloadSettingPanels(self, changedSliceMode = False):
481 self.leftSizer.Detach(self.simpleSettingsPanel)
482 self.leftSizer.Detach(self.normalSettingsPanel)
483 self.simpleSettingsPanel.Destroy()
484 self.normalSettingsPanel.Destroy()
485 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, self.simpleModeUpdated)
486 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, self.scene.sceneUpdated)
487 self.leftSizer.Add(self.simpleSettingsPanel, 1)
488 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
489 self.updateSliceMode(changedSliceMode)
490 self.updateProfileToAllControls()
493 def updateMachineMenu(self):
494 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
495 for item in self.machineMenu.GetMenuItems():
496 self.machineMenu.RemoveItem(item)
498 #Add a menu item for each machine configuration.
499 for n in xrange(0, profile.getMachineCount()):
500 i = self.machineMenu.Append(n + 0x1000, profile.getMachineName(n).title(), kind=wx.ITEM_RADIO)
501 if n == int(profile.getPreferenceFloat('active_machine')):
503 self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
505 self.machineMenu.AppendSeparator()
506 i = self.machineMenu.Append(-1, _("Add new machine..."))
507 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
508 i = self.machineMenu.Append(-1, _("Machine settings..."))
509 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
511 #Add tools for machines.
512 self.machineMenu.AppendSeparator()
514 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
515 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
517 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
518 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
520 def OnLoadProfile(self, e):
521 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)
522 dlg.SetWildcard("ini files (*.ini)|*.ini")
523 if dlg.ShowModal() == wx.ID_OK:
524 profileFile = dlg.GetPath()
525 profile.loadProfile(profileFile)
526 self.updateProfileToAllControls()
528 # Update the Profile MRU
529 self.addToProfileMRU(profileFile)
532 def OnLoadProfileFromGcode(self, e):
533 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)
534 dlg.SetWildcard("gcode files (*%s)|*%s;*%s" % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
535 if dlg.ShowModal() == wx.ID_OK:
536 gcodeFile = dlg.GetPath()
537 f = open(gcodeFile, 'r')
540 if line.startswith(';CURA_PROFILE_STRING:'):
541 profile.setProfileFromString(line[line.find(':')+1:].strip())
542 if ';{profile_string}' not in profile.getAlterationFile('end.gcode'):
543 profile.setAlterationFile('end.gcode', profile.getAlterationFile('end.gcode') + '\n;{profile_string}')
546 self.updateProfileToAllControls()
548 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)
551 def OnSaveProfile(self, e):
552 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
553 dlg.SetWildcard("ini files (*.ini)|*.ini")
554 if dlg.ShowModal() == wx.ID_OK:
555 profile_filename = dlg.GetPath()
556 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
557 profile_filename += '.ini'
558 profile.saveProfile(profile_filename)
561 def OnSaveDifferences(self, e):
562 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
563 dlg.SetWildcard("ini files (*.ini)|*.ini")
564 if dlg.ShowModal() == wx.ID_OK:
565 profile_filename = dlg.GetPath()
566 if not profile_filename.lower().endswith('.ini'): #hack for linux, as for some reason the .ini is not appended.
567 profile_filename += '.ini'
568 profile.saveProfileDifferenceFromDefault(profile_filename)
571 def OnResetProfile(self, e):
572 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)
573 result = dlg.ShowModal() == wx.ID_YES
576 profile.resetProfile()
577 self.updateProfileToAllControls()
579 def OnSimpleSwitch(self, e):
580 profile.putPreference('startMode', 'Simple')
581 self.updateSliceMode()
583 def OnNormalSwitch(self, e):
584 profile.putPreference('startMode', 'Normal')
585 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)
586 result = dlg.ShowModal() == wx.ID_YES
589 profile.resetProfile()
590 for k, v in self.simpleSettingsPanel.getSettingOverrides().items():
591 if profile.isProfileSetting(k):
592 profile.putProfileSetting(k, v)
593 elif profile.isAlterationSetting(k):
594 profile.setAlterationFile(k, v)
595 self.updateProfileToAllControls()
596 self.updateSliceMode()
598 def OnDefaultMarlinFirmware(self, e):
599 firmwareInstall.InstallFirmware(self)
601 def OnCustomFirmware(self, e):
602 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
603 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)
604 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
605 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
606 if dlg.ShowModal() == wx.ID_OK:
607 filename = dlg.GetPath()
609 if not(os.path.exists(filename)):
611 #For some reason my Ubuntu 10.10 crashes here.
612 firmwareInstall.InstallFirmware(self, filename)
614 def OnAddNewMachine(self, e):
616 wasSimple = profile.getPreference('startMode') == 'Simple'
617 configWizard.ConfigWizard(True)
618 isSimple = profile.getPreference('startMode') == 'Simple'
620 self.reloadSettingPanels(isSimple != wasSimple)
621 self.updateMachineMenu()
623 def OnSelectMachine(self, index):
624 profile.setActiveMachine(index)
625 self.reloadSettingPanels(False)
627 def OnBedLevelWizard(self, e):
628 configWizard.bedLevelWizard()
630 def OnHeadOffsetWizard(self, e):
631 configWizard.headOffsetWizard()
633 def OnExpertOpen(self, e):
634 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
638 def OnMinecraftImport(self, e):
639 mi = minecraftImport.minecraftImportWindow(self)
643 def OnPIDDebugger(self, e):
644 debugger = pidDebugger.debuggerWindow(self)
648 def OnAutoFirmwareUpdate(self, e):
649 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
650 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
651 if dlg.ShowModal() == wx.ID_OK:
652 filename = dlg.GetPath()
654 if not(os.path.exists(filename)):
656 #For some reason my Ubuntu 10.10 crashes here.
657 installer = firmwareInstall.AutoUpdateFirmware(self, filename)
659 def onCopyProfileClipboard(self, e):
661 if not wx.TheClipboard.IsOpened():
662 wx.TheClipboard.Open()
663 clipData = wx.TextDataObject()
664 self.lastTriedClipboard = profile.getProfileString()
665 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
666 clipData.SetText(profileString)
667 wx.TheClipboard.SetData(clipData)
668 wx.TheClipboard.Close()
670 print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
672 def OnCheckForUpdate(self, e):
673 newVersion = version.checkForNewerVersion()
674 if newVersion is not None:
675 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:
676 webbrowser.open(newVersion)
678 wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
680 def OnAbout(self, e):
681 aboutBox = aboutWindow.aboutWindow(self)
686 def OnClose(self, e):
687 profile.saveProfile(profile.getDefaultProfilePath(), True)
689 # Save the window position, size & state from the preferences file
690 profile.putPreference('window_maximized', self.IsMaximized())
691 if not self.IsMaximized() and not self.IsIconized():
692 (posx, posy) = self.GetPosition()
693 profile.putPreference('window_pos_x', posx)
694 profile.putPreference('window_pos_y', posy)
695 (width, height) = self.GetSize()
696 profile.putPreference('window_width', width)
697 profile.putPreference('window_height', height)
699 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
700 isSimple = profile.getPreference('startMode') == 'Simple'
702 self.normalSashPos = self.splitter.GetSashPosition()
703 profile.putPreference('window_normal_sash', self.normalSashPos)
705 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
707 self.scene.OnPaint = lambda e : e
714 class normalSettingsPanel(configBase.configPanelBase):
715 "Main user interface window"
716 def __init__(self, parent, callback = None):
717 super(normalSettingsPanel, self).__init__(parent, callback)
720 self.nb = wx.Notebook(self)
721 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
722 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
724 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, _('Basic'))
725 self._addSettingsToPanels('basic', left, right)
726 self.SizeLabelWidths(left, right)
728 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, _('Advanced'))
729 self._addSettingsToPanels('advanced', left, right)
730 self.SizeLabelWidths(left, right)
733 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
734 self.nb.AddPage(self.pluginPanel, _("Plugins"))
737 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
738 self.alterationPanel = None
740 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
741 self.nb.AddPage(self.alterationPanel, _("Start/End-GCode"))
743 self.Bind(wx.EVT_SIZE, self.OnSize)
745 self.nb.SetSize(self.GetSize())
746 self.UpdateSize(self.printPanel)
747 self.UpdateSize(self.advancedPanel)
749 def _addSettingsToPanels(self, category, left, right):
750 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
754 for title in profile.getSubCategoriesFor(category):
755 n += 1 + len(profile.getSettingsForCategory(category, title))
758 configBase.TitleRow(p, _(title))
759 for s in profile.getSettingsForCategory(category, title):
760 configBase.SettingRow(p, s.getName())
762 def SizeLabelWidths(self, left, right):
763 leftWidth = self.getLabelColumnWidth(left)
764 rightWidth = self.getLabelColumnWidth(right)
765 maxWidth = max(leftWidth, rightWidth)
766 self.setLabelColumnWidth(left, maxWidth)
767 self.setLabelColumnWidth(right, maxWidth)
770 # Make the size of the Notebook control the same size as this control
771 self.nb.SetSize(self.GetSize())
773 # Propegate the OnSize() event (just in case)
776 # Perform out resize magic
777 self.UpdateSize(self.printPanel)
778 self.UpdateSize(self.advancedPanel)
780 def UpdateSize(self, configPanel):
781 sizer = configPanel.GetSizer()
785 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
788 # if width(col1) > (best_width(col1) + best_width(col1)):
789 # switch to horizontal
792 col1 = configPanel.leftPanel
793 colSize1 = col1.GetSize()
794 colBestSize1 = col1.GetBestSize()
795 col2 = configPanel.rightPanel
796 colSize2 = col2.GetSize()
797 colBestSize2 = col2.GetBestSize()
799 orientation = sizer.GetOrientation()
801 if orientation == wx.HORIZONTAL:
802 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
804 sizer = wx.BoxSizer(wx.VERTICAL)
805 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
806 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
807 configPanel.SetSizer(sizer)
813 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
815 sizer = wx.BoxSizer(wx.HORIZONTAL)
816 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
817 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
818 configPanel.SetSizer(sizer)
824 def updateProfileToControls(self):
825 super(normalSettingsPanel, self).updateProfileToControls()
826 if self.alterationPanel is not None:
827 self.alterationPanel.updateProfileToControls()
828 self.pluginPanel.updateProfileToControls()