1 from __future__ import absolute_import
7 from Cura.gui import configBase
8 from Cura.gui import expertConfig
9 from Cura.gui import preview3d
10 from Cura.gui import sliceProgessPanel
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 printWindow
17 from Cura.gui import simpleMode
18 from Cura.gui import projectPlanner
19 from Cura.gui.tools import batchRun
20 from Cura.gui import flatSlicerWindow
21 from Cura.gui.util import dropTarget
22 from Cura.gui.tools import minecraftImport
23 from Cura.util import validators
24 from Cura.util import profile
25 from Cura.util import version
26 from Cura.util import sliceRun
27 from Cura.util import meshLoader
29 class mainWindow(wx.Frame):
31 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
33 self.extruderCount = int(profile.getPreference('extruder_amount'))
35 wx.EVT_CLOSE(self, self.OnClose)
37 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
39 self.normalModeOnlyItems = []
41 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
42 self.config = wx.FileConfig(appName="Cura",
43 localFilename=mruFile,
44 style=wx.CONFIG_USE_LOCAL_FILE)
46 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)]
47 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
48 self.config.SetPath("/ModelMRU")
49 self.modelFileHistory.Load(self.config)
51 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)]
52 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
53 self.config.SetPath("/ProfileMRU")
54 self.profileFileHistory.Load(self.config)
56 self.menubar = wx.MenuBar()
57 self.fileMenu = wx.Menu()
58 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
59 self.Bind(wx.EVT_MENU, lambda e: self._showModelLoadDialog(1), i)
60 i = self.fileMenu.Append(-1, 'Prepare print...\tCTRL+R')
61 self.Bind(wx.EVT_MENU, self.OnSlice, i)
62 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
63 self.Bind(wx.EVT_MENU, self.OnPrint, i)
65 self.fileMenu.AppendSeparator()
66 i = self.fileMenu.Append(-1, 'Open Profile...')
67 self.normalModeOnlyItems.append(i)
68 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
69 i = self.fileMenu.Append(-1, 'Save Profile...')
70 self.normalModeOnlyItems.append(i)
71 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
72 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
73 self.normalModeOnlyItems.append(i)
74 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
75 self.fileMenu.AppendSeparator()
76 i = self.fileMenu.Append(-1, 'Reset Profile to default')
77 self.normalModeOnlyItems.append(i)
78 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
80 self.fileMenu.AppendSeparator()
81 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
82 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
83 self.fileMenu.AppendSeparator()
86 modelHistoryMenu = wx.Menu()
87 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
88 self.modelFileHistory.UseMenu(modelHistoryMenu)
89 self.modelFileHistory.AddFilesToMenu()
90 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
93 profileHistoryMenu = wx.Menu()
94 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
95 self.profileFileHistory.UseMenu(profileHistoryMenu)
96 self.profileFileHistory.AddFilesToMenu()
97 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
99 self.fileMenu.AppendSeparator()
100 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
101 self.Bind(wx.EVT_MENU, self.OnQuit, i)
102 self.menubar.Append(self.fileMenu, '&File')
104 toolsMenu = wx.Menu()
105 i = toolsMenu.Append(-1, 'Switch to quickprint...')
106 self.switchToQuickprintMenuItem = i
107 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
108 i = toolsMenu.Append(-1, 'Switch to full settings...')
109 self.switchToNormalMenuItem = i
110 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
111 toolsMenu.AppendSeparator()
112 i = toolsMenu.Append(-1, 'Batch run...')
113 self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
114 i = toolsMenu.Append(-1, 'Project planner...')
115 self.Bind(wx.EVT_MENU, self.OnProjectPlanner, i)
116 # i = toolsMenu.Append(-1, 'Open SVG (2D) slicer...')
117 # self.Bind(wx.EVT_MENU, self.OnSVGSlicerOpen, i)
118 if minecraftImport.hasMinecraft():
119 i = toolsMenu.Append(-1, 'Minecraft import...')
120 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
121 self.menubar.Append(toolsMenu, 'Tools')
123 expertMenu = wx.Menu()
124 i = expertMenu.Append(-1, 'Open expert settings...')
125 self.normalModeOnlyItems.append(i)
126 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
127 expertMenu.AppendSeparator()
128 if firmwareInstall.getDefaultFirmware() is not None:
129 i = expertMenu.Append(-1, 'Install default Marlin firmware')
130 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
131 i = expertMenu.Append(-1, 'Install custom firmware')
132 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
133 expertMenu.AppendSeparator()
134 i = expertMenu.Append(-1, 'Run first run wizard...')
135 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
136 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
137 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
138 self.menubar.Append(expertMenu, 'Expert')
141 i = helpMenu.Append(-1, 'Online documentation...')
142 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
143 i = helpMenu.Append(-1, 'Report a problem...')
144 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
145 i = helpMenu.Append(-1, 'Check for update...')
146 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
147 self.menubar.Append(helpMenu, 'Help')
148 self.SetMenuBar(self.menubar)
150 if profile.getPreference('lastFile') != '':
151 self.filelist = profile.getPreference('lastFile').split(';')
152 self.SetTitle('Cura - %s - %s' % (version.getVersion(), self.filelist[-1]))
155 self.progressPanelList = []
158 self.simpleSettingsPanel = simpleMode.simpleModePanel(self)
159 self.normalSettingsPanel = normalSettingsPanel(self)
162 self.preview3d = preview3d.previewPanel(self)
164 # load and slice buttons.
165 loadButton = wx.Button(self, -1, '&Load model')
166 sliceButton = wx.Button(self, -1, 'P&repare print')
167 printButton = wx.Button(self, -1, '&Print')
168 self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(1), loadButton)
169 self.Bind(wx.EVT_BUTTON, self.OnSlice, sliceButton)
170 self.Bind(wx.EVT_BUTTON, self.OnPrint, printButton)
172 if self.extruderCount > 1:
173 loadButton2 = wx.Button(self, -1, 'Load Dual')
174 self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(2), loadButton2)
175 if self.extruderCount > 2:
176 loadButton3 = wx.Button(self, -1, 'Load Triple')
177 self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(3), loadButton3)
178 if self.extruderCount > 3:
179 loadButton4 = wx.Button(self, -1, 'Load Quad')
180 self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(4), loadButton4)
182 #Also bind double clicking the 3D preview to load an STL file.
183 self.preview3d.glCanvas.Bind(wx.EVT_LEFT_DCLICK, lambda e: self._showModelLoadDialog(1), self.preview3d.glCanvas)
185 #Main sizer, to position the preview window, buttons and tab control
186 sizer = wx.GridBagSizer()
188 sizer.Add(self.preview3d, (0,1), span=(1,2+self.extruderCount), flag=wx.EXPAND)
189 sizer.AddGrowableCol(2 + self.extruderCount)
190 sizer.AddGrowableRow(0)
191 sizer.Add(loadButton, (1,1), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
192 if self.extruderCount > 1:
193 sizer.Add(loadButton2, (1,2), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
194 if self.extruderCount > 2:
195 sizer.Add(loadButton3, (1,3), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
196 if self.extruderCount > 3:
197 sizer.Add(loadButton4, (1,4), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
198 sizer.Add(sliceButton, (1,1+self.extruderCount), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
199 sizer.Add(printButton, (1,2+self.extruderCount), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
202 if len(self.filelist) > 0:
203 self.preview3d.loadModelFiles(self.filelist)
205 self.updateProfileToControls()
207 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
209 self.simpleSettingsPanel.Show(False)
210 self.normalSettingsPanel.Show(False)
211 self.updateSliceMode()
213 # Set default window size & position
214 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
217 # Restore the window position, size & state from the preferences file
219 if profile.getPreference('window_maximized') == 'True':
222 posx = int(profile.getPreference('window_pos_x'))
223 posy = int(profile.getPreference('window_pos_y'))
224 width = int(profile.getPreference('window_width'))
225 height = int(profile.getPreference('window_height'))
226 self.SetPosition((posx,posy))
227 self.SetSize((width,height))
233 def updateSliceMode(self):
234 isSimple = profile.getPreference('startMode') == 'Simple'
236 self.normalSettingsPanel.Show(not isSimple)
237 self.simpleSettingsPanel.Show(isSimple)
239 self.GetSizer().Detach(self.simpleSettingsPanel)
240 self.GetSizer().Detach(self.normalSettingsPanel)
242 self.GetSizer().Add(self.simpleSettingsPanel, (0,0), span=(1,1), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=6)
244 self.GetSizer().Add(self.normalSettingsPanel, (0,0), span=(1,1), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=6)
246 for i in self.normalModeOnlyItems:
247 i.Enable(not isSimple)
248 self.switchToQuickprintMenuItem.Enable(not isSimple)
249 self.switchToNormalMenuItem.Enable(isSimple)
251 self.normalSettingsPanel.Layout()
252 self.simpleSettingsPanel.Layout()
253 self.GetSizer().Layout()
256 def OnPreferences(self, e):
257 prefDialog = preferencesDialog.preferencesDialog(self)
259 prefDialog.Show(True)
261 def _showOpenDialog(self, title, wildcard = meshLoader.wildcardFilter()):
262 dlg=wx.FileDialog(self, title, os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
263 dlg.SetWildcard(wildcard)
264 if dlg.ShowModal() == wx.ID_OK:
265 filename = dlg.GetPath()
267 if not(os.path.exists(filename)):
269 profile.putPreference('lastFile', filename)
274 def _showModelLoadDialog(self, amount):
276 for i in xrange(0, amount):
277 filelist.append(self._showOpenDialog("Open file to print"))
278 if filelist[-1] == False:
280 self._loadModels(filelist)
282 def _loadModels(self, filelist):
283 self.filelist = filelist
284 self.SetTitle(filelist[-1] + ' - Cura - ' + version.getVersion())
285 profile.putPreference('lastFile', ';'.join(self.filelist))
286 self.preview3d.loadModelFiles(self.filelist, True)
287 self.preview3d.setViewMode("Normal")
288 # Update the Model MRU
289 for idx in xrange(0, len(self.filelist)):
290 self.modelFileHistory.AddFileToHistory(self.filelist[idx])
291 self.config.SetPath("/ModelMRU")
292 self.modelFileHistory.Save(self.config)
295 def OnDropFiles(self, files):
296 self._loadModels(files)
298 def OnLoadModel(self, e):
299 self._showModelLoadDialog(1)
301 def OnLoadModel2(self, e):
302 self._showModelLoadDialog(2)
304 def OnLoadModel3(self, e):
305 self._showModelLoadDialog(3)
307 def OnLoadModel4(self, e):
308 self._showModelLoadDialog(4)
310 def OnSlice(self, e):
311 if len(self.filelist) < 1:
312 wx.MessageBox('You need to load a file before you can prepare it.', 'Print error', wx.OK | wx.ICON_INFORMATION)
314 isSimple = profile.getPreference('startMode') == 'Simple'
316 #save the current profile so we can put it back latter
317 oldProfile = profile.getGlobalProfileString()
318 self.simpleSettingsPanel.setupSlice()
319 #Create a progress panel and add it to the window. The progress panel will start the Skein operation.
320 spp = sliceProgessPanel.sliceProgessPanel(self, self, self.filelist)
321 self.sizer.Add(spp, (len(self.progressPanelList)+2,0), span=(1, 3 + self.extruderCount), flag=wx.EXPAND)
323 newSize = self.GetSize()
324 newSize.IncBy(0, spp.GetSize().GetHeight())
325 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
326 self.SetSize(newSize)
327 self.progressPanelList.append(spp)
329 profile.loadGlobalProfileFromString(oldProfile)
331 def OnPrint(self, e):
332 if len(self.filelist) < 1:
333 wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
335 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
336 wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
338 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
340 def OnModelMRU(self, e):
341 fileNum = e.GetId() - self.ID_MRU_MODEL1
342 path = self.modelFileHistory.GetHistoryFile(fileNum)
344 self.modelFileHistory.AddFileToHistory(path) # move up the list
345 self.config.SetPath("/ModelMRU")
346 self.modelFileHistory.Save(self.config)
350 self._loadModels(filelist)
352 def OnProfileMRU(self, e):
353 fileNum = e.GetId() - self.ID_MRU_PROFILE1
354 path = self.profileFileHistory.GetHistoryFile(fileNum)
356 self.profileFileHistory.AddFileToHistory(path) # move up the list
357 self.config.SetPath("/ProfileMRU")
358 self.profileFileHistory.Save(self.config)
361 profile.loadGlobalProfile(path)
362 self.updateProfileToControls()
364 def removeSliceProgress(self, spp):
365 self.progressPanelList.remove(spp)
366 newSize = self.GetSize()
367 newSize.IncBy(0, -spp.GetSize().GetHeight())
368 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
369 self.SetSize(newSize)
371 self.sizer.Detach(spp)
372 for spp in self.progressPanelList:
373 self.sizer.Detach(spp)
375 for spp in self.progressPanelList:
376 self.sizer.Add(spp, (i,0), span=(1,3 + self.extruderCount), flag=wx.EXPAND)
380 def updateProfileToControls(self):
381 self.preview3d.updateProfileToControls()
382 self.normalSettingsPanel.updateProfileToControls()
383 self.simpleSettingsPanel.updateProfileToControls()
385 def OnLoadProfile(self, e):
386 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)
387 dlg.SetWildcard("ini files (*.ini)|*.ini")
388 if dlg.ShowModal() == wx.ID_OK:
389 profileFile = dlg.GetPath()
390 profile.loadGlobalProfile(profileFile)
391 self.updateProfileToControls()
393 # Update the Profile MRU
394 self.profileFileHistory.AddFileToHistory(profileFile)
395 self.config.SetPath("/ProfileMRU")
396 self.profileFileHistory.Save(self.config)
400 def OnLoadProfileFromGcode(self, e):
401 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)
402 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
403 if dlg.ShowModal() == wx.ID_OK:
404 gcodeFile = dlg.GetPath()
405 f = open(gcodeFile, 'r')
408 if line.startswith(';CURA_PROFILE_STRING:'):
409 profile.loadGlobalProfileFromString(line[line.find(':')+1:].strip())
412 self.updateProfileToControls()
414 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)
417 def OnSaveProfile(self, e):
418 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
419 dlg.SetWildcard("ini files (*.ini)|*.ini")
420 if dlg.ShowModal() == wx.ID_OK:
421 profileFile = dlg.GetPath()
422 profile.saveGlobalProfile(profileFile)
425 def OnResetProfile(self, e):
426 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)
427 result = dlg.ShowModal() == wx.ID_YES
430 profile.resetGlobalProfile()
431 self.updateProfileToControls()
433 def OnBatchRun(self, e):
434 br = batchRun.batchRunWindow(self)
438 def OnSimpleSwitch(self, e):
439 profile.putPreference('startMode', 'Simple')
440 self.updateSliceMode()
442 def OnNormalSwitch(self, e):
443 profile.putPreference('startMode', 'Normal')
444 self.updateSliceMode()
446 def OnDefaultMarlinFirmware(self, e):
447 firmwareInstall.InstallFirmware()
449 def OnCustomFirmware(self, e):
450 if profile.getPreference('machine_type') == 'ultimaker':
451 wx.MessageBox('Warning: Installing a custom firmware does not garantee that you machine will function correctly, and could damage your machine.', 'Firmware update', wx.OK | wx.ICON_EXCLAMATION)
452 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
453 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
454 if dlg.ShowModal() == wx.ID_OK:
455 filename = dlg.GetPath()
456 if not(os.path.exists(filename)):
458 #For some reason my Ubuntu 10.10 crashes here.
459 firmwareInstall.InstallFirmware(filename)
461 def OnFirstRunWizard(self, e):
462 configWizard.configWizard()
463 self.updateProfileToControls()
465 def OnBedLevelWizard(self, e):
466 configWizard.bedLevelWizard()
468 def OnExpertOpen(self, e):
469 ecw = expertConfig.expertConfigWindow()
473 def OnProjectPlanner(self, e):
474 pp = projectPlanner.projectPlanner()
478 def OnMinecraftImport(self, e):
479 mi = minecraftImport.minecraftImportWindow(self)
483 def OnSVGSlicerOpen(self, e):
484 svgSlicer = flatSlicerWindow.flatSlicerWindow()
488 def OnCheckForUpdate(self, e):
489 newVersion = version.checkForNewerVersion()
490 if newVersion is not None:
491 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:
492 webbrowser.open(newVersion)
494 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
496 def OnClose(self, e):
497 profile.saveGlobalProfile(profile.getDefaultProfilePath())
499 # Save the window position, size & state from the preferences file
500 profile.putPreference('window_maximized', self.IsMaximized())
501 if not self.IsMaximized():
502 (posx, posy) = self.GetPosition()
503 profile.putPreference('window_pos_x', posx)
504 profile.putPreference('window_pos_y', posy)
505 (width, height) = self.GetSize()
506 profile.putPreference('window_width', width)
507 profile.putPreference('window_height', height)
514 class normalSettingsPanel(configBase.configPanelBase):
515 "Main user interface window"
516 def __init__(self, parent):
517 super(normalSettingsPanel, self).__init__(parent)
520 nb = wx.Notebook(self)
521 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
522 self.GetSizer().Add(nb, 1)
524 (left, right) = self.CreateConfigTab(nb, 'Print config')
526 configBase.TitleRow(left, "Quality")
527 c = configBase.SettingRow(left, "Layer height (mm)", 'layer_height', '0.2', 'Layer height in millimeters.\n0.2 is a good value for quick prints.\n0.1 gives high quality prints.')
528 validators.validFloat(c, 0.0001)
529 validators.warningAbove(c, lambda : (float(profile.getProfileSetting('nozzle_size')) * 80.0 / 100.0), "Thicker layers then %.2fmm (80%% nozzle size) usually give bad results and are not recommended.")
530 c = configBase.SettingRow(left, "Wall thickness (mm)", 'wall_thickness', '0.8', 'Thickness of the walls.\nThis is used in combination with the nozzle size to define the number\nof perimeter lines and the thickness of those perimeter lines.')
531 validators.validFloat(c, 0.0001)
532 validators.wallThicknessValidator(c)
533 c = configBase.SettingRow(left, "Enable retraction", 'retraction_enable', False, 'Retract the filament when the nozzle is moving over a none-printed area. Details about the retraction can be configured in the advanced tab.')
535 configBase.TitleRow(left, "Fill")
536 c = configBase.SettingRow(left, "Bottom/Top thickness (mm)", 'solid_layer_thickness', '0.6', 'This controls the thickness of the bottom and top layers, the amount of solid layers put down is calculated by the layer thickness and this value.\nHaving this value a multiply of the layer thickness makes sense. And keep it near your wall thickness to make an evenly strong part.')
537 validators.validFloat(c, 0.0)
538 c = configBase.SettingRow(left, "Fill Density (%)", 'fill_density', '20', 'This controls how densily filled the insides of your print will be. For a solid part use 100%, for an empty part use 0%. A value around 20% is usually enough')
539 validators.validFloat(c, 0.0, 100.0)
541 configBase.TitleRow(right, "Speed && Temperature")
542 c = configBase.SettingRow(right, "Print speed (mm/s)", 'print_speed', '50', 'Speed at which printing happens. A well adjusted Ultimaker can reach 150mm/s, but for good quality prints you want to print slower. Printing speed depends on a lot of factors. So you will be experimenting with optimal settings for this.')
543 validators.validFloat(c, 1.0)
544 validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s")
545 validators.printSpeedValidator(c)
547 #configBase.TitleRow(right, "Temperature")
548 c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself')
549 validators.validFloat(c, 0.0, 340.0)
550 validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
551 if profile.getPreference('has_heated_bed') == 'True':
552 c = configBase.SettingRow(right, "Bed temperature", 'print_bed_temperature', '0', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself')
553 validators.validFloat(c, 0.0, 340.0)
555 configBase.TitleRow(right, "Support structure")
556 c = configBase.SettingRow(right, "Support type", 'support', ['None', 'Exterior Only', 'Everywhere'], 'Type of support structure build.\n"Exterior only" is the most commonly used support setting.\n\nNone does not do any support.\nExterior only only creates support where the support structure will touch the build platform.\nEverywhere creates support even on the insides of the model.')
557 c = configBase.SettingRow(right, "Add raft", 'enable_raft', False, 'A raft is a few layers of lines below the bottom of the object. It prevents warping. Full raft settings can be found in the expert settings.\nFor PLA this is usually not required. But if you print with ABS it is almost required.')
558 if int(profile.getPreference('extruder_amount')) > 1:
559 c = configBase.SettingRow(right, "Support dual extrusion", 'support_dual_extrusion', False, 'Print the support material with the 2nd extruder in a dual extrusion setup. The primary extruder will be used for normal material, while the second extruder is used to print support material.')
561 configBase.TitleRow(right, "Filament")
562 c = configBase.SettingRow(right, "Diameter (mm)", 'filament_diameter', '2.89', 'Diameter of your filament, as accurately as possible.\nIf you cannot measure this value you will have to callibrate it, a higher number means less extrusion, a smaller number generates more extrusion.')
563 validators.validFloat(c, 1.0)
564 validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
565 c = configBase.SettingRow(right, "Packing Density", 'filament_density', '1.00', 'Packing density of your filament. This should be 1.00 for PLA and 0.85 for ABS')
566 validators.validFloat(c, 0.5, 1.5)
568 (left, right) = self.CreateConfigTab(nb, 'Advanced config')
570 configBase.TitleRow(left, "Machine size")
571 c = configBase.SettingRow(left, "Nozzle size (mm)", 'nozzle_size', '0.4', 'The nozzle size is very important, this is used to calculate the line width of the infill, and used to calculate the amount of outside wall lines and thickness for the wall thickness you entered in the print settings.')
572 validators.validFloat(c, 0.1, 10.0)
574 configBase.TitleRow(left, "Skirt")
575 c = configBase.SettingRow(left, "Line count", 'skirt_line_count', '1', 'The skirt is a line drawn around the object at the first layer. This helps to prime your extruder, and to see if the object fits on your platform.\nSetting this to 0 will disable the skirt. Multiple skirt lines can help priming your extruder better for small objects.')
576 validators.validInt(c, 0, 10)
577 c = configBase.SettingRow(left, "Start distance (mm)", 'skirt_gap', '6.0', 'The distance between the skirt and the first layer.\nThis is the minimal distance, multiple skirt lines will be put outwards from this distance.')
578 validators.validFloat(c, 0.0)
580 configBase.TitleRow(left, "Retraction")
581 c = configBase.SettingRow(left, "Minimum travel (mm)", 'retraction_min_travel', '5.0', 'Minimum amount of travel needed for a retraction to happen at all. To make sure you do not get a lot of retractions in a small area')
582 validators.validFloat(c, 0.0)
583 c = configBase.SettingRow(left, "Speed (mm/s)", 'retraction_speed', '40.0', 'Speed at which the filament is retracted, a higher retraction speed works better. But a very high retraction speed can lead to filament grinding.')
584 validators.validFloat(c, 0.1)
585 c = configBase.SettingRow(left, "Distance (mm)", 'retraction_amount', '0.0', 'Amount of retraction, set at 0 for no retraction at all. A value of 2.0mm seems to generate good results.')
586 validators.validFloat(c, 0.0)
587 c = configBase.SettingRow(left, "Extra length on start (mm)", 'retraction_extra', '0.0', 'Extra extrusion amount when restarting after a retraction, to better "Prime" your extruder after retraction.')
588 validators.validFloat(c, 0.0)
590 configBase.TitleRow(right, "Speed")
591 c = configBase.SettingRow(right, "Travel speed (mm/s)", 'travel_speed', '150', 'Speed at which travel moves are done, a high quality build Ultimaker can reach speeds of 250mm/s. But some machines might miss steps then.')
592 validators.validFloat(c, 1.0)
593 validators.warningAbove(c, 300.0, "It is highly unlikely that your machine can achieve a travel speed above 300mm/s")
594 c = configBase.SettingRow(right, "Max Z speed (mm/s)", 'max_z_speed', '1.0', 'Speed at which Z moves are done. When you Z axis is properly lubercated you can increase this for less Z blob.')
595 validators.validFloat(c, 0.5)
596 c = configBase.SettingRow(right, "Bottom layer speed (mm/s)", 'bottom_layer_speed', '25', 'Print speed for the bottom layer, you want to print the first layer slower so it sticks better to the printer bed.')
597 validators.validFloat(c, 0.0)
599 configBase.TitleRow(right, "Cool")
600 c = configBase.SettingRow(right, "Minimal layer time (sec)", 'cool_min_layer_time', '10', 'Minimum time spend in a layer, gives the layer time to cool down before the next layer is put on top. If the layer will be placed down too fast the printer will slow down to make sure it has spend atleast this amount of seconds printing this layer.')
601 validators.validFloat(c, 0.0)
602 c = configBase.SettingRow(right, "Enable cooling fan", 'fan_enabled', True, 'Enable the cooling fan during the print. The extra cooling from the cooling fan is essensial during faster prints.')
604 configBase.TitleRow(right, "Quality")
605 c = configBase.SettingRow(right, "Initial layer thickness (mm)", 'bottom_thickness', '0.0', 'Layer thickness of the bottom layer. A thicker bottom layer makes sticking to the bed easier. Set to 0.0 to have the bottom layer thickness the same as the other layers.')
606 validators.validFloat(c, 0.0)
607 validators.warningAbove(c, lambda : (float(profile.getProfileSetting('nozzle_size')) * 3.0 / 4.0), "A bottom layer of more then %.2fmm (3/4 nozzle size) usually give bad results and is not recommended.")
608 c = configBase.SettingRow(right, "Duplicate outlines", 'enable_skin', False, 'Skin prints the outer lines of the prints twice, each time with half the thickness. This gives the illusion of a higher print quality.')
611 self.pluginPanel = pluginPanel.pluginPanel(nb)
612 if len(self.pluginPanel.pluginList) > 0:
613 nb.AddPage(self.pluginPanel, "Plugins")
615 self.pluginPanel.Show(False)
618 self.alterationPanel = alterationPanel.alterationPanel(nb)
619 nb.AddPage(self.alterationPanel, "Start/End-GCode")
621 def updateProfileToControls(self):
622 super(normalSettingsPanel, self).updateProfileToControls()
623 self.alterationPanel.updateProfileToControls()
624 self.pluginPanel.updateProfileToControls()