1 from __future__ import absolute_import
3 import wx, os, multiprocessing, threading, time, shutil
5 from Cura.util import profile
6 from Cura.util import sliceRun
7 from Cura.util import meshLoader
8 from Cura.util import gcodeInterpreter
9 from Cura.gui.util import dropTarget
11 class batchRunWindow(wx.Frame):
12 def __init__(self, parent):
13 super(batchRunWindow, self).__init__(parent, title='Cura - Batch run')
17 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions()))
19 wx.EVT_CLOSE(self, self.OnClose)
20 self.panel = wx.Panel(self, -1)
21 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
22 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
24 self.sizer = wx.GridBagSizer(2,2)
25 self.panel.SetSizer(self.sizer)
27 self.listbox = wx.ListBox(self.panel, -1, choices=[])
28 self.addButton = wx.Button(self.panel, -1, "Add")
29 self.remButton = wx.Button(self.panel, -1, "Remove")
30 self.sliceButton = wx.Button(self.panel, -1, "Prepare all")
32 self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)
33 self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)
34 self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)
35 self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)
37 self.sizer.Add(self.listbox, (0,0), span=(1,3), flag=wx.EXPAND)
38 self.sizer.Add(self.addButton, (1,0), span=(1,1))
39 self.sizer.Add(self.remButton, (1,1), span=(1,1))
40 self.sizer.Add(self.sliceButton, (1,2), span=(1,1))
42 self.sizer.AddGrowableCol(2)
43 self.sizer.AddGrowableRow(0)
45 def OnAddModel(self, e):
46 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)
47 dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")
48 if dlg.ShowModal() == wx.ID_OK:
49 for filename in dlg.GetPaths():
50 profile.putPreference('lastFile', filename)
51 self.list.append(filename)
52 self.selection = filename
56 def OnDropFiles(self, filenames):
57 for filename in filenames:
58 profile.putPreference('lastFile', filename)
59 self.list.append(filename)
60 self.selection = filename
63 def OnRemModel(self, e):
64 if self.selection is None:
66 self.list.remove(self.selection)
69 def OnListSelect(self, e):
70 if self.listbox.GetSelection() == -1:
72 self.selection = self.list[self.listbox.GetSelection()]
74 def _updateListbox(self):
76 for item in self.list:
77 self.listbox.AppendAndEnsureVisible(os.path.split(item)[1])
78 if self.selection in self.list:
79 self.listbox.SetSelection(self.list.index(self.selection))
80 elif len(self.list) > 0:
81 self.selection = self.list[0]
82 self.listbox.SetSelection(0)
85 self.listbox.SetSelection(-1)
92 outputFilenameList = []
93 center = profile.getMachineCenterCoords() + profile.getObjectMatrix()
94 for filename in self.list:
95 outputFilename = sliceRun.getExportFilename(filename)
96 outputFilenameList.append(outputFilename)
97 sliceCmdList.append(sliceRun.getSliceCommand(outputFilename, [filename], [center]))
98 bspw = BatchSliceProgressWindow(self.list[:], outputFilenameList, sliceCmdList)
102 class BatchSliceProgressWindow(wx.Frame):
103 def __init__(self, filenameList, outputFilenameList, sliceCmdList):
104 super(BatchSliceProgressWindow, self).__init__(None, title='Cura')
105 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
107 self.filenameList = filenameList
108 self.outputFilenameList = outputFilenameList
109 self.sliceCmdList = sliceCmdList
111 self.sliceStartTime = time.time()
114 self.threadCount = multiprocessing.cpu_count() - 1
117 if self.threadCount < 1:
119 if self.threadCount > len(self.sliceCmdList):
120 self.threadCount = len(self.sliceCmdList)
124 self.totalDoneFactor = []
125 for i in xrange(0, self.threadCount):
126 self.prevStep.append('start')
127 self.totalDoneFactor.append(0.0)
129 self.sizer = wx.GridBagSizer(2, 2)
130 self.progressGauge = []
132 for i in xrange(0, self.threadCount):
133 self.statusText.append(wx.StaticText(self, -1, "Building: %d " % (len(self.sliceCmdList))))
134 self.progressGauge.append(wx.Gauge(self, -1))
135 self.progressGauge[i].SetRange(10000)
136 self.progressTextTotal = wx.StaticText(self, -1, "Done: 0/%d " % (len(self.sliceCmdList)))
137 self.progressGaugeTotal = wx.Gauge(self, -1)
138 self.progressGaugeTotal.SetRange(len(self.sliceCmdList))
139 self.abortButton = wx.Button(self, -1, "Abort")
140 for i in xrange(0, self.threadCount):
141 self.sizer.Add(self.statusText[i], (i*2,0), span=(1,4))
142 self.sizer.Add(self.progressGauge[i], (1+i*2, 0), span=(1,4), flag=wx.EXPAND)
143 self.sizer.Add(self.progressTextTotal, (self.threadCount*2,0), span=(1,4))
144 self.sizer.Add(self.progressGaugeTotal, (1+self.threadCount*2, 0), span=(1,4), flag=wx.EXPAND)
146 self.sizer.Add(self.abortButton, (2+self.threadCount*2,0), span=(1,4), flag=wx.ALIGN_CENTER)
147 self.sizer.AddGrowableCol(0)
148 self.sizer.AddGrowableRow(0)
150 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)
151 self.SetSizer(self.sizer)
155 threading.Thread(target=self.OnRunManager).start()
157 def OnAbort(self, e):
162 self.abortButton.SetLabel('Close')
164 def SetProgress(self, index, stepName, layer, maxLayer):
165 if self.prevStep[index] != stepName:
166 self.totalDoneFactor[index] += sliceRun.sliceStepTimeFactor[self.prevStep[index]]
167 newTime = time.time()
168 self.prevStep[index] = stepName
170 progresValue = ((self.totalDoneFactor[index] + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000
171 self.progressGauge[index].SetValue(int(progresValue))
172 self.statusText[index].SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")
174 def OnRunManager(self):
176 for i in xrange(0, self.threadCount):
177 threads.append(threading.Thread(target=self.OnRun, args=(i,)))
185 sliceTime = time.time() - self.sliceStartTime
186 status = "Build: %d models" % (len(self.sliceCmdList))
187 status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)
189 wx.CallAfter(self.statusText[0].SetLabel, status)
190 wx.CallAfter(self.OnSliceDone)
192 def OnRun(self, index):
193 while self.cmdIndex < len(self.sliceCmdList):
194 cmdIndex = self.cmdIndex;
196 action = self.sliceCmdList[cmdIndex]
197 wx.CallAfter(self.SetTitle, "Building: [%d/%d]" % (self.sliceCmdList.index(action) + 1, len(self.sliceCmdList)))
199 p = sliceRun.startSliceCommandProcess(action)
200 line = p.stdout.readline()
202 while(len(line) > 0):
204 if line[0:9] == "Progress[" and line[-1:] == "]":
205 progress = line[9:-1].split(":")
206 if len(progress) > 2:
207 maxValue = int(progress[2])
208 wx.CallAfter(self.SetProgress, index, progress[0], int(progress[1]), maxValue)
210 wx.CallAfter(self.statusText[index].SetLabel, line)
213 wx.CallAfter(self.statusText[index].SetLabel, "Aborted by user.")
215 line = p.stdout.readline()
216 self.returnCode = p.wait()
218 # Update output gocde file...
219 # Warning: the user could have changed the profile between the slcer run and this code. We might be using old information.
220 gcodeFilename = self.outputFilenameList[index]
221 gcode = gcodeInterpreter.gcode()
222 gcode.load(gcodeFilename)
223 profile.replaceGCodeTags(gcodeFilename, gcode)
225 wx.CallAfter(self.progressGauge[index].SetValue, 10000)
226 self.totalDoneFactor[index] = 0.0
227 wx.CallAfter(self.progressTextTotal.SetLabel, "Done %d/%d" % (self.cmdIndex, len(self.sliceCmdList)))
228 wx.CallAfter(self.progressGaugeTotal.SetValue, self.cmdIndex)
230 def OnSliceDone(self):
231 self.abortButton.Destroy()
232 self.closeButton = wx.Button(self, -1, "Close")
233 self.sizer.Add(self.closeButton, (2+self.threadCount*2,0), span=(1,1))
234 if profile.getPreference('sdpath') != '':
235 self.copyToSDButton = wx.Button(self, -1, "To SDCard")
236 self.Bind(wx.EVT_BUTTON, self.OnCopyToSD, self.copyToSDButton)
237 self.sizer.Add(self.copyToSDButton, (2+self.threadCount*2,1), span=(1,1))
238 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)
242 def OnCopyToSD(self, e):
243 for f in self.filenameList:
244 exportFilename = sliceRun.getExportFilename(f)
245 filename = os.path.basename(exportFilename)
246 if profile.getPreference('sdshortnames') == 'True':
247 filename = sliceRun.getShortFilename(filename)
248 shutil.copy(exportFilename, os.path.join(profile.getPreference('sdpath'), filename))