diff --git a/Cura/gui/printWindow.py b/Cura/gui/printWindow.py index 274d6bf..6eaeb3e 100644 --- a/Cura/gui/printWindow.py +++ b/Cura/gui/printWindow.py @@ -6,6 +6,7 @@ from wx.lib import buttons from gui import icon from gui import toolbarUtil +from gui import webcam from util import machineCom from util import profile from util import gcodeInterpreter @@ -18,7 +19,6 @@ def printFile(filename): printWindowMonitorHandle = printProcessMonitor() printWindowMonitorHandle.loadFile(filename) - def startPrintInterface(filename): #startPrintInterface is called from the main script when we want the printer interface to run in a seperate process. # It needs to run in a seperate process, as any running python code blocks the GCode sender pyton code (http://wiki.python.org/moin/GlobalInterpreterLock). @@ -97,6 +97,12 @@ class printWindow(wx.Frame): self.pause = False self.termHistory = [] self.termHistoryIdx = 0 + + self.cam = None + try: + self.cam = webcam.webcam() + except: + pass #self.SetIcon(icon.getMainIcon()) @@ -128,7 +134,7 @@ class printWindow(wx.Frame): self.sizer.Add(self.progress, pos=(5,0), span=(1,2), flag=wx.EXPAND) nb = wx.Notebook(self.panel) - self.sizer.Add(nb, pos=(0,3), span=(7,4)) + self.sizer.Add(nb, pos=(0,3), span=(7,4), flag=wx.EXPAND) self.temperaturePanel = wx.Panel(nb) sizer = wx.GridBagSizer(2, 2) @@ -233,9 +239,20 @@ class printWindow(wx.Frame): sizer.AddGrowableRow(0) nb.AddPage(self.termPanel, 'Term') + + if self.cam != None: + self.camPage = wx.Panel(nb) + sizer = wx.GridBagSizer(2, 2) + self.camPage.SetSizer(sizer) + + nb.AddPage(self.camPage, 'Camera') + self.camPage.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPage.timer) + self.camPage.timer.Start(500) + self.camPage.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground) self.sizer.AddGrowableRow(3) - self.sizer.AddGrowableCol(0) + self.sizer.AddGrowableCol(3) self.Bind(wx.EVT_CLOSE, self.OnClose) self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect) @@ -261,6 +278,23 @@ class printWindow(wx.Frame): self.UpdateButtonStates() self.UpdateProgress() + def OnCameraTimer(self, e): + if self.printIdx != None: + return + self.cam.takeNewImage() + self.camPage.Refresh() + + def OnCameraEraseBackground(self, e): + dc = e.GetDC() + if not dc: + dc = wx.ClientDC(self) + rect = self.GetUpdateRegion().GetBox() + dc.SetClippingRect(rect) + dc.SetBackground(wx.Brush(self.camPage.GetBackgroundColour(), wx.SOLID)) + dc.Clear() + if self.cam.getLastImage() != None: + dc.DrawBitmap(self.cam.getLastImage(), 0, 0) + def UpdateButtonStates(self): self.connectButton.Enable(not self.machineConnected) #self.loadButton.Enable(self.printIdx == None) @@ -287,6 +321,7 @@ class printWindow(wx.Frame): status += 'Line: -/%d\n' % (len(self.gcodeList)) else: status += 'Line: %d/%d\n' % (self.printIdx, len(self.gcodeList)) + status += 'Height: %f\n' % (self.currentZ) self.progress.SetValue(self.printIdx) if self.temp != None: status += 'Temp: %d\n' % (self.temp) @@ -317,12 +352,17 @@ class printWindow(wx.Frame): return if self.printIdx != None: return + self.currentZ = -1 + if self.cam != None: + self.cam.startTimelaps(self.filename[: self.filename.rfind('.')] + ".mpg") self.printIdx = 1 self.sendLine(0) self.sendCnt = self.bufferLineCount self.UpdateButtonStates() def OnCancel(self, e): + if self.cam != None: + self.cam.endTimelaps() self.printIdx = None self.pause = False self.pauseButton.SetLabel('Pause') @@ -405,6 +445,7 @@ class printWindow(wx.Frame): gcode = gcodeInterpreter.gcode() gcode.loadList(gcodeList) print "Loaded: %s (%d)" % (filename, len(gcodeList)) + self.filename = filename self.gcode = gcode self.gcodeList = gcodeList self.typeList = typeList @@ -442,6 +483,12 @@ class printWindow(wx.Frame): line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self.feedrateRatioFill)), line) if self.typeList[lineNr] == 'SUPPORT': line = re.sub('F([0-9]*)', lambda m: 'F' + str(int(int(m.group(1)) * self.feedrateRatioSupport)), line) + if 'G1' in line and 'Z' in line: + z = float(re.search('Z([0-9\.]*)', line).group(1)) + if self.cam != None and self.currentZ != z: + wx.CallAfter(self.cam.takeNewImage) + wx.CallAfter(self.camPage.Refresh) + self.currentZ = z except: print "Unexpected error:", sys.exc_info() checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNr, line))) @@ -487,6 +534,8 @@ class printWindow(wx.Frame): if self.sendLine(self.printIdx): self.printIdx += 1 else: + if self.cam != None: + self.cam.endTimelaps() self.printIdx = None wx.CallAfter(self.UpdateButtonStates) wx.CallAfter(self.UpdateProgress) @@ -576,6 +625,10 @@ class temperatureGraph(wx.Panel): self.points.pop(0) def addPoint(self, temp, tempSP, bedTemp, bedTempSP): + if bedTemp == None: + bedTemp = 0 + if bedTempSP == None: + bedTempSP = 0 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time())) wx.CallAfter(self.UpdateDrawing) diff --git a/Cura/gui/webcam.py b/Cura/gui/webcam.py new file mode 100644 index 0000000..5b5a946 --- /dev/null +++ b/Cura/gui/webcam.py @@ -0,0 +1,66 @@ +import os, glob, subprocess +import wx + +try: + #Use the vidcap library directly from the VideoCapture package. (Windows only) + # http://videocapture.sourceforge.net/ + # We're using the binary interface, not the python interface, so we don't depend on PIL + import vidcap as win32vidcap +except: + win32vidcap = None + +#TODO: We can also use OpenCV for camera capture. This should be cross platform compatible. + +class webcam(object): + def __init__(self): + if win32vidcap != None: + self._cam = win32vidcap.new_Dev(0, False) + #self._cam.displaycapturefilterproperties() + #self._cam.displaycapturepinproperties() + else: + raise exception("No camera implementation available") + + self._doTimelaps = False + self._bitmap = None + + def takeNewImage(self): + buffer, width, height = self._cam.getbuffer() + wxImage = wx.EmptyImage(width, height) + wxImage.SetData(buffer[::-1]) + if self._bitmap != None: + del self._bitmap + self._bitmap = wxImage.ConvertToBitmap() + del wxImage + del buffer + + if self._doTimelaps: + filename = os.path.normpath(os.path.join(os.path.split(__file__)[0], "../__tmp_snap", "__tmp_snap_%04d.jpg" % (self._snapshotCount))) + self._snapshotCount += 1 + self._bitmap.SaveFile(filename, wx.BITMAP_TYPE_JPEG) + + return self._bitmap + + def getLastImage(self): + return self._bitmap + + def startTimelaps(self, filename): + self._cleanTempDir() + self._timelapsFilename = filename + self._snapshotCount = 0 + self._doTimelaps = True + + def endTimelaps(self): + if self._doTimelaps: + ffmpeg = os.path.normpath(os.path.join(os.path.split(__file__)[0], "../ffmpeg.exe")) + basePath = os.path.normpath(os.path.join(os.path.split(__file__)[0], "../__tmp_snap", "__tmp_snap_%04d.jpg")) + subprocess.call([ffmpeg, '-r', '12.5', '-i', basePath, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', '25', '-y', '-b:v', '1500k', '-f', 'vob', self._timelapsFilename]) + self._doTimelaps = False + + def _cleanTempDir(self): + basePath = os.path.normpath(os.path.join(os.path.split(__file__)[0], "../__tmp_snap")) + try: + os.makedirs(basePath) + except: + pass + for filename in glob.iglob(basePath + "/*.jpg"): + os.remove(filename) diff --git a/Cura/util/machineCom.py b/Cura/util/machineCom.py index 459522c..087b4eb 100644 --- a/Cura/util/machineCom.py +++ b/Cura/util/machineCom.py @@ -44,7 +44,7 @@ class VirtualPrinter(): def write(self, data): if self.readList == None: return - print "Send: %s" % (data.rstrip()) + #print "Send: %s" % (data.rstrip()) if 'M104' in data or 'M109' in data: try: self.targetTemp = float(data[data.find('S')+1:]) @@ -78,7 +78,7 @@ class VirtualPrinter(): if self.readList == None: return '' time.sleep(0.01) - print "Recv: %s" % (self.readList[0].rstrip()) + #print "Recv: %s" % (self.readList[0].rstrip()) return self.readList.pop(0) def close(self):