chiark / gitweb /
Add proper copyright statements.
[cura.git] / Cura / gui / tools / batchRun.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2 from __future__ import absolute_import
3
4 import wx, os, multiprocessing, threading, time, shutil
5
6 from Cura.util import profile
7 from Cura.util import sliceRun
8 from Cura.util import meshLoader
9 from Cura.util import gcodeInterpreter
10 from Cura.gui.util import dropTarget
11
12 class batchRunWindow(wx.Frame):
13         def __init__(self, parent):
14                 super(batchRunWindow, self).__init__(parent, title='Cura - Batch run')
15                 
16                 self.list = []
17                 
18                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions()))
19                 
20                 wx.EVT_CLOSE(self, self.OnClose)
21                 self.panel = wx.Panel(self, -1)
22                 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
23                 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
24
25                 self.sizer = wx.GridBagSizer(2,2)
26                 self.panel.SetSizer(self.sizer)
27
28                 self.listbox = wx.ListBox(self.panel, -1, choices=[])
29                 self.addButton = wx.Button(self.panel, -1, "Add")
30                 self.remButton = wx.Button(self.panel, -1, "Remove")
31                 self.sliceButton = wx.Button(self.panel, -1, "Prepare all")
32
33                 self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)
34                 self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)
35                 self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)
36                 self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)
37
38                 self.sizer.Add(self.listbox, (0,0), span=(1,3), flag=wx.EXPAND)
39                 self.sizer.Add(self.addButton, (1,0), span=(1,1))
40                 self.sizer.Add(self.remButton, (1,1), span=(1,1))
41                 self.sizer.Add(self.sliceButton, (1,2), span=(1,1))
42
43                 self.sizer.AddGrowableCol(2)
44                 self.sizer.AddGrowableRow(0)
45
46         def OnAddModel(self, e):
47                 dlg=wx.FileDialog(self, "Open file to batch prepare", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
48                 dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")
49                 if dlg.ShowModal() == wx.ID_OK:
50                         for filename in dlg.GetPaths():
51                                 profile.putPreference('lastFile', filename)
52                                 self.list.append(filename)
53                                 self.selection = filename
54                                 self._updateListbox()
55                 dlg.Destroy()
56         
57         def OnDropFiles(self, filenames):
58                 for filename in filenames:
59                         profile.putPreference('lastFile', filename)
60                         self.list.append(filename)
61                         self.selection = filename
62                         self._updateListbox()
63
64         def OnRemModel(self, e):
65                 if self.selection is None:
66                         return
67                 self.list.remove(self.selection)
68                 self._updateListbox()
69
70         def OnListSelect(self, e):
71                 if self.listbox.GetSelection() == -1:
72                         return
73                 self.selection = self.list[self.listbox.GetSelection()]
74
75         def _updateListbox(self):
76                 self.listbox.Clear()
77                 for item in self.list:
78                         self.listbox.AppendAndEnsureVisible(os.path.split(item)[1])
79                 if self.selection in self.list:
80                         self.listbox.SetSelection(self.list.index(self.selection))
81                 elif len(self.list) > 0:
82                         self.selection = self.list[0]
83                         self.listbox.SetSelection(0)
84                 else:
85                         self.selection = None
86                         self.listbox.SetSelection(-1)
87
88         def OnClose(self, e):
89                 self.Destroy()
90
91         def OnSlice(self, e):
92                 sliceCmdList = []
93                 outputFilenameList = []
94                 center = profile.getMachineCenterCoords() + profile.getObjectMatrix()
95                 for filename in self.list:
96                         outputFilename = sliceRun.getExportFilename(filename)
97                         outputFilenameList.append(outputFilename)
98                         sliceCmdList.append(sliceRun.getSliceCommand(outputFilename, [filename], [center]))
99                 bspw = BatchSliceProgressWindow(self.list[:], outputFilenameList, sliceCmdList)
100                 bspw.Centre()
101                 bspw.Show(True)
102         
103 class BatchSliceProgressWindow(wx.Frame):
104         def __init__(self, filenameList, outputFilenameList, sliceCmdList):
105                 super(BatchSliceProgressWindow, self).__init__(None, title='Cura')
106                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
107                 
108                 self.filenameList = filenameList
109                 self.outputFilenameList = outputFilenameList
110                 self.sliceCmdList = sliceCmdList
111                 self.abort = False
112                 self.sliceStartTime = time.time()
113
114                 try:
115                         self.threadCount = multiprocessing.cpu_count() - 1
116                 except:
117                         self.threadCount = 1
118                 if self.threadCount < 1:
119                         self.threadCount = 1
120                 if self.threadCount > len(self.sliceCmdList):
121                         self.threadCount = len(self.sliceCmdList)
122                 self.cmdIndex = 0
123                 
124                 self.prevStep = []
125                 self.totalDoneFactor = []
126                 for i in xrange(0, self.threadCount):
127                         self.prevStep.append('start')
128                         self.totalDoneFactor.append(0.0)
129
130                 self.sizer = wx.GridBagSizer(2, 2) 
131                 self.progressGauge = []
132                 self.statusText = []
133                 for i in xrange(0, self.threadCount):
134                         self.statusText.append(wx.StaticText(self, -1, "Building: %d                           " % (len(self.sliceCmdList))))
135                         self.progressGauge.append(wx.Gauge(self, -1))
136                         self.progressGauge[i].SetRange(10000)
137                 self.progressTextTotal = wx.StaticText(self, -1, "Done: 0/%d                           " % (len(self.sliceCmdList)))
138                 self.progressGaugeTotal = wx.Gauge(self, -1)
139                 self.progressGaugeTotal.SetRange(len(self.sliceCmdList))
140                 self.abortButton = wx.Button(self, -1, "Abort")
141                 for i in xrange(0, self.threadCount):
142                         self.sizer.Add(self.statusText[i], (i*2,0), span=(1,4))
143                         self.sizer.Add(self.progressGauge[i], (1+i*2, 0), span=(1,4), flag=wx.EXPAND)
144                 self.sizer.Add(self.progressTextTotal, (self.threadCount*2,0), span=(1,4))
145                 self.sizer.Add(self.progressGaugeTotal, (1+self.threadCount*2, 0), span=(1,4), flag=wx.EXPAND)
146
147                 self.sizer.Add(self.abortButton, (2+self.threadCount*2,0), span=(1,4), flag=wx.ALIGN_CENTER)
148                 self.sizer.AddGrowableCol(0)
149                 self.sizer.AddGrowableRow(0)
150
151                 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)
152                 self.SetSizer(self.sizer)
153                 self.Layout()
154                 self.Fit()
155                 
156                 threading.Thread(target=self.OnRunManager).start()
157
158         def OnAbort(self, e):
159                 if self.abort:
160                         self.Close()
161                 else:
162                         self.abort = True
163                         self.abortButton.SetLabel('Close')
164
165         def SetProgress(self, index, stepName, layer, maxLayer):
166                 if self.prevStep[index] != stepName:
167                         self.totalDoneFactor[index] += sliceRun.sliceStepTimeFactor[self.prevStep[index]]
168                         newTime = time.time()
169                         self.prevStep[index] = stepName
170                 
171                 progresValue = ((self.totalDoneFactor[index] + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000
172                 self.progressGauge[index].SetValue(int(progresValue))
173                 self.statusText[index].SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")
174         
175         def OnRunManager(self):
176                 threads = []
177                 for i in xrange(0, self.threadCount):
178                         threads.append(threading.Thread(target=self.OnRun, args=(i,)))
179
180                 for t in threads:
181                         t.start()
182                 for t in threads:
183                         t.join()
184
185                 self.abort = True
186                 sliceTime = time.time() - self.sliceStartTime
187                 status = "Build: %d models" % (len(self.sliceCmdList))
188                 status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)
189
190                 wx.CallAfter(self.statusText[0].SetLabel, status)
191                 wx.CallAfter(self.OnSliceDone)
192         
193         def OnRun(self, index):
194                 while self.cmdIndex < len(self.sliceCmdList):
195                         cmdIndex = self.cmdIndex;
196                         self.cmdIndex += 1                      
197                         action = self.sliceCmdList[cmdIndex]
198                         wx.CallAfter(self.SetTitle, "Building: [%d/%d]"  % (self.sliceCmdList.index(action) + 1, len(self.sliceCmdList)))
199
200                         p = sliceRun.startSliceCommandProcess(action)
201                         line = p.stdout.readline()
202                         maxValue = 1
203                         while(len(line) > 0):
204                                 line = line.rstrip()
205                                 if line[0:9] == "Progress[" and line[-1:] == "]":
206                                         progress = line[9:-1].split(":")
207                                         if len(progress) > 2:
208                                                 maxValue = int(progress[2])
209                                         wx.CallAfter(self.SetProgress, index, progress[0], int(progress[1]), maxValue)
210                                 else:
211                                         wx.CallAfter(self.statusText[index].SetLabel, line)
212                                 if self.abort:
213                                         p.terminate()
214                                         wx.CallAfter(self.statusText[index].SetLabel, "Aborted by user.")
215                                         return
216                                 line = p.stdout.readline()
217                         self.returnCode = p.wait()
218
219                         # Update output gocde file...
220                         # Warning: the user could have changed the profile between the slcer run and this code.  We might be using old information.
221                         gcodeFilename = self.outputFilenameList[index]
222                         gcode = gcodeInterpreter.gcode()
223                         gcode.load(gcodeFilename)
224                         profile.replaceGCodeTags(gcodeFilename, gcode)
225                         
226                         wx.CallAfter(self.progressGauge[index].SetValue, 10000)
227                         self.totalDoneFactor[index] = 0.0
228                         wx.CallAfter(self.progressTextTotal.SetLabel, "Done %d/%d" % (self.cmdIndex, len(self.sliceCmdList)))
229                         wx.CallAfter(self.progressGaugeTotal.SetValue, self.cmdIndex)
230         
231         def OnSliceDone(self):
232                 self.abortButton.Destroy()
233                 self.closeButton = wx.Button(self, -1, "Close")
234                 self.sizer.Add(self.closeButton, (2+self.threadCount*2,0), span=(1,1))
235                 if profile.getPreference('sdpath') != '':
236                         self.copyToSDButton = wx.Button(self, -1, "To SDCard")
237                         self.Bind(wx.EVT_BUTTON, self.OnCopyToSD, self.copyToSDButton)
238                         self.sizer.Add(self.copyToSDButton, (2+self.threadCount*2,1), span=(1,1))
239                 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)
240                 self.Layout()
241                 self.Fit()
242
243         def OnCopyToSD(self, e):
244                 for f in self.filenameList:
245                         exportFilename = sliceRun.getExportFilename(f)
246                         filename = os.path.basename(exportFilename)
247                         if profile.getPreference('sdshortnames') == 'True':
248                                 filename = sliceRun.getShortFilename(filename)
249                         shutil.copy(exportFilename, os.path.join(profile.getPreference('sdpath'), filename))