chiark / gitweb /
Add webcam timelaps support for windows.
authorDaid <daid303@gmail.com>
Sun, 2 Sep 2012 12:27:24 +0000 (14:27 +0200)
committerDaid <daid303@gmail.com>
Sun, 2 Sep 2012 12:27:24 +0000 (14:27 +0200)
Cura/gui/printWindow.py
Cura/gui/webcam.py [new file with mode: 0644]
Cura/util/machineCom.py

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