chiark / gitweb /
Split gui to gui and util directories.
[cura.git] / Cura / gui / mainWindow.py
1 from __future__ import absolute_import
2 import __init__
3
4 import wx, os, platform, types, webbrowser
5
6 from gui import configBase
7 from gui import advancedConfig
8 from gui import preview3d
9 from gui import sliceProgessPanel
10 from gui import alterationPanel
11 from gui import validators
12 from gui import preferencesDialog
13 from gui import configWizard
14 from gui import machineCom
15 from gui import printWindow
16 from util import profile
17
18 def main():
19         app = wx.App(False)
20         if profile.getPreference('wizardDone') == 'False':
21                 if os.name == 'darwin':
22                         wx.MessageBox('The MacOS version of Cura is experimental.\nThere are still UI/usability bugs. Check the issue list at:\nhttps://github.com/daid/Cura/issues\nfor details.\nPlease report any extra issue you find.', 'MacOS Warning', wx.OK | wx.ICON_INFORMATION)
23                 configWizard.configWizard()
24                 profile.putPreference("wizardDone", "True")
25         mainWindow()
26         app.MainLoop()
27
28 class mainWindow(configBase.configWindowBase):
29         "Main user interface window"
30         def __init__(self):
31                 super(mainWindow, self).__init__(title='Cura')
32                 
33                 wx.EVT_CLOSE(self, self.OnClose)
34                 
35                 menubar = wx.MenuBar()
36                 fileMenu = wx.Menu()
37                 i = fileMenu.Append(-1, 'Load model file...')
38                 self.Bind(wx.EVT_MENU, self.OnLoadModel, i)
39                 fileMenu.AppendSeparator()
40                 i = fileMenu.Append(-1, 'Open Profile...')
41                 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
42                 i = fileMenu.Append(-1, 'Save Profile...')
43                 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
44                 fileMenu.AppendSeparator()
45                 i = fileMenu.Append(-1, 'Preferences...')
46                 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
47                 fileMenu.AppendSeparator()
48                 i = fileMenu.Append(wx.ID_EXIT, 'Quit')
49                 self.Bind(wx.EVT_MENU, self.OnQuit, i)
50                 menubar.Append(fileMenu, '&File')
51                 
52                 expertMenu = wx.Menu()
53                 i = expertMenu.Append(-1, 'Open expert settings...')
54                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
55                 expertMenu.AppendSeparator()
56                 i = expertMenu.Append(-1, 'Install default Marlin firmware')
57                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
58                 i = expertMenu.Append(-1, 'Install custom firmware')
59                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
60                 expertMenu.AppendSeparator()
61                 i = expertMenu.Append(-1, 'ReRun first run wizard...')
62                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
63                 menubar.Append(expertMenu, 'Expert')
64                 
65                 helpMenu = wx.Menu()
66                 i = helpMenu.Append(-1, 'Online documentation...')
67                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/wiki'), i)
68                 i = helpMenu.Append(-1, 'Report a problem...')
69                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
70                 menubar.Append(helpMenu, 'Help')
71                 self.SetMenuBar(menubar)
72                 
73                 self.lastPath = ""
74                 self.filename = profile.getPreference('lastFile')
75                 self.progressPanelList = []
76
77                 #Preview window
78                 self.preview3d = preview3d.previewPanel(self)
79
80                 #Main tabs
81                 nb = wx.Notebook(self)
82                 
83                 (left, right) = self.CreateConfigTab(nb, 'Print config')
84                 
85                 configBase.TitleRow(left, "Accuracy")
86                 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.')
87                 validators.validFloat(c, 0.0)
88                 validators.warningAbove(c, lambda : (float(profile.getProfileSetting('nozzle_size')) * 80 / 100), "Thicker layers then %.2fmm (80%% nozzle size) usually give bad results and are not recommended.")
89                 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.')
90                 validators.validFloat(c, 0.0)
91                 validators.wallThicknessValidator(c)
92                 
93                 configBase.TitleRow(left, "Fill")
94                 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.')
95                 validators.validFloat(c, 0.0)
96                 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')
97                 validators.validFloat(c, 0.0, 100.0)
98                 
99                 configBase.TitleRow(left, "Skirt")
100                 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.')
101                 validators.validInt(c, 0, 10)
102                 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.')
103                 validators.validFloat(c, 0.0)
104
105                 configBase.TitleRow(right, "Speed")
106                 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.')
107                 validators.validFloat(c, 1.0)
108                 validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s")
109                 validators.printSpeedValidator(c)
110                 
111                 configBase.TitleRow(right, "Temperature")
112                 c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself')
113                 validators.validFloat(c, 0.0, 340.0)
114                 validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
115                 
116                 configBase.TitleRow(right, "Support")
117                 c = configBase.SettingRow(right, "Support type", 'support', ['None', 'Exterior Only', 'Everywhere', 'Empty Layers Only'], 'Type of support structure build.\nNone does not do any support.\nExterior only only creates support on the outside.\nEverywhere creates support even on the insides of the model.\nOnly on empty layers is for stacked objects.')
118                 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 advanced settings.\nFor PLA this is usually not required. But if you print with ABS it is almost required.')
119
120                 configBase.TitleRow(right, "Filament")
121                 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.')
122                 validators.validFloat(c, 1.0)
123                 validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
124                 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')
125                 validators.validFloat(c, 0.5, 1.5)
126                 
127                 (left, right) = self.CreateConfigTab(nb, 'Advanced config')
128                 
129                 configBase.TitleRow(left, "Machine size")
130                 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.')
131                 validators.validFloat(c, 0.1, 1.0)
132                 c = configBase.SettingRow(left, "Machine center X (mm)", 'machine_center_x', '100', 'The center of your machine, your print will be placed at this location')
133                 validators.validInt(c, 10)
134                 configBase.settingNotify(c, self.preview3d.updateCenterX)
135                 c = configBase.SettingRow(left, "Machine center Y (mm)", 'machine_center_y', '100', 'The center of your machine, your print will be placed at this location')
136                 validators.validInt(c, 10)
137                 configBase.settingNotify(c, self.preview3d.updateCenterY)
138
139                 configBase.TitleRow(left, "Retraction")
140                 c = configBase.SettingRow(left, "Minimal travel (mm)", 'retraction_min_travel', '5.0', 'Minimal 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')
141                 validators.validFloat(c, 0.0)
142                 c = configBase.SettingRow(left, "Speed (mm/s)", 'retraction_speed', '13.5', 'Speed at which the filament is retracted')
143                 validators.validFloat(c, 0.1)
144                 c = configBase.SettingRow(left, "Distance (mm)", 'retraction_amount', '0.0', 'Amount of retraction, set at 0 for no retraction at all.')
145                 validators.validFloat(c, 0.0)
146                 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')
147                 validators.validFloat(c, 0.0)
148
149                 configBase.TitleRow(right, "Speed")
150                 c = configBase.SettingRow(right, "Travel speed (mm/s)", 'travel_speed', '150', 'Speed at which travel moves are done')
151                 validators.validFloat(c, 1.0)
152                 validators.warningAbove(c, 300.0, "It is highly unlikely that your machine can achieve a travel speed above 150mm/s")
153                 c = configBase.SettingRow(right, "Max Z speed (mm/s)", 'max_z_speed', '1.0', 'Speed at which Z moves are done.')
154                 validators.validFloat(c, 0.5)
155                 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.')
156                 validators.validFloat(c, 0.0)
157
158                 configBase.TitleRow(right, "Cool")
159                 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.')
160                 validators.validFloat(c, 0.0)
161
162                 nb.AddPage(alterationPanel.alterationPanel(nb), "Start/End-GCode")
163
164                 # load and slice buttons.
165                 loadButton = wx.Button(self, -1, 'Load Model')
166                 sliceButton = wx.Button(self, -1, 'Slice to GCode')
167                 printButton = wx.Button(self, -1, 'Print GCode')
168                 self.Bind(wx.EVT_BUTTON, self.OnLoadModel, loadButton)
169                 self.Bind(wx.EVT_BUTTON, self.OnSlice, sliceButton)
170                 self.Bind(wx.EVT_BUTTON, self.OnPrint, printButton)
171                 #Also bind double clicking the 3D preview to load an STL file.
172                 self.preview3d.glCanvas.Bind(wx.EVT_LEFT_DCLICK, self.OnLoadModel, self.preview3d.glCanvas)
173
174                 #Main sizer, to position the preview window, buttons and tab control
175                 sizer = wx.GridBagSizer()
176                 self.SetSizer(sizer)
177                 sizer.Add(nb, (0,0), span=(1,1), flag=wx.EXPAND)
178                 sizer.Add(self.preview3d, (0,1), span=(1,3), flag=wx.EXPAND)
179                 sizer.AddGrowableCol(2)
180                 sizer.AddGrowableRow(0)
181                 sizer.Add(loadButton, (1,1), flag=wx.RIGHT, border=5)
182                 sizer.Add(sliceButton, (1,2), flag=wx.RIGHT, border=5)
183                 sizer.Add(printButton, (1,3), flag=wx.RIGHT, border=5)
184                 self.sizer = sizer
185
186                 if self.filename != "None":
187                         self.preview3d.loadModelFile(self.filename)
188                         self.lastPath = os.path.split(self.filename)[0]
189
190                 self.updateProfileToControls()
191
192                 self.Fit()
193                 self.SetMinSize(self.GetSize())
194                 self.Centre()
195                 self.Show(True)
196         
197         def OnLoadProfile(self, e):
198                 dlg=wx.FileDialog(self, "Select profile file to load", self.lastPath, style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
199                 dlg.SetWildcard("ini files (*.ini)|*.ini")
200                 if dlg.ShowModal() == wx.ID_OK:
201                         profileFile = dlg.GetPath()
202                         self.lastPath = os.path.split(profileFile)[0]
203                         profile.loadGlobalProfile(profileFile)
204                         self.updateProfileToControls()
205                 dlg.Destroy()
206         
207         def OnSaveProfile(self, e):
208                 dlg=wx.FileDialog(self, "Select profile file to save", self.lastPath, style=wx.FD_SAVE)
209                 dlg.SetWildcard("ini files (*.ini)|*.ini")
210                 if dlg.ShowModal() == wx.ID_OK:
211                         profileFile = dlg.GetPath()
212                         self.lastPath = os.path.split(profileFile)[0]
213                         profile.saveGlobalProfile(profileFile)
214                 dlg.Destroy()
215         
216         def OnPreferences(self, e):
217                 prefDialog = preferencesDialog.preferencesDialog(self)
218                 prefDialog.Centre()
219                 prefDialog.Show(True)
220         
221         def OnDefaultMarlinFirmware(self, e):
222                 machineCom.InstallFirmware(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../firmware/default.hex"))
223
224         def OnCustomFirmware(self, e):
225                 dlg=wx.FileDialog(self, "Open firmware to upload", self.lastPath, style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
226                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
227                 if dlg.ShowModal() == wx.ID_OK:
228                         filename = dlg.GetPath()
229                         if not(os.path.exists(filename)):
230                                 return
231                         #For some reason my Ubuntu 10.10 crashes here.
232                         machineCom.InstallFirmware(filename)
233
234         def OnFirstRunWizard(self, e):
235                 configWizard.configWizard()
236                 self.updateProfileToControls()
237
238         def OnLoadModel(self, e):
239                 dlg=wx.FileDialog(self, "Open file to print", self.lastPath, style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
240                 dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")
241                 if dlg.ShowModal() == wx.ID_OK:
242                         self.filename=dlg.GetPath()
243                         profile.putPreference('lastFile', self.filename)
244                         if not(os.path.exists(self.filename)):
245                                 return
246                         self.lastPath = os.path.split(self.filename)[0]
247                         self.preview3d.loadModelFile(self.filename)
248                         self.preview3d.setViewMode("Model - Normal")
249                 dlg.Destroy()
250         
251         def OnSlice(self, e):
252                 if self.filename == None:
253                         return
254                 profile.saveGlobalProfile(profile.getDefaultProfilePath())
255                 
256                 #Create a progress panel and add it to the window. The progress panel will start the Skein operation.
257                 spp = sliceProgessPanel.sliceProgessPanel(self, self, self.filename)
258                 self.sizer.Add(spp, (len(self.progressPanelList)+2,0), span=(1,4), flag=wx.EXPAND)
259                 self.sizer.Layout()
260                 newSize = self.GetSize();
261                 newSize.IncBy(0, spp.GetSize().GetHeight())
262                 self.SetSize(newSize)
263                 self.progressPanelList.append(spp)
264         
265         def OnPrint(self, e):
266                 printWindow.printWindow()
267
268         def OnExpertOpen(self, e):
269                 acw = advancedConfig.advancedConfigWindow()
270                 acw.Centre()
271                 acw.Show(True)
272
273         def removeSliceProgress(self, spp):
274                 self.progressPanelList.remove(spp)
275                 newSize = self.GetSize();
276                 newSize.IncBy(0, -spp.GetSize().GetHeight())
277                 self.SetSize(newSize)
278                 self.sizer.Remove(spp)
279                 spp.Destroy()
280                 for spp in self.progressPanelList:
281                         self.sizer.Remove(spp)
282                 i = 2
283                 for spp in self.progressPanelList:
284                         self.sizer.Add(spp, (i,0), span=(1,4), flag=wx.EXPAND)
285                         i += 1
286                 self.sizer.Layout()
287
288         def OnQuit(self, e):
289                 self.Close()
290         
291         def OnClose(self, e):
292                 profile.saveGlobalProfile(profile.getDefaultProfilePath())
293                 self.Destroy()