chiark / gitweb /
Add youmagine uploader (inactive, no API urls filled)
authordaid <daid303@gmail.com>
Mon, 12 Aug 2013 12:54:18 +0000 (14:54 +0200)
committerdaid <daid303@gmail.com>
Mon, 12 Aug 2013 12:54:18 +0000 (14:54 +0200)
Cura/gui/mainWindow.py
Cura/gui/tools/youmagineGui.py [new file with mode: 0644]
Cura/gui/util/webcam.py
Cura/util/mesh.py
Cura/util/meshLoaders/amf.py
Cura/util/meshLoaders/stl.py
Cura/util/profile.py
Cura/util/youmagine.py [new file with mode: 0644]

index 0ce1827dfdc562c799ae1123d06b8d9c5f25d329..e432b8a81533d06ac892f797ae9c8183e52d1f09 100644 (file)
@@ -18,9 +18,11 @@ from Cura.gui.util import dropTarget
 #from Cura.gui.tools import batchRun
 from Cura.gui.tools import pidDebugger
 from Cura.gui.tools import minecraftImport
+from Cura.gui.tools import youmagineGui
 from Cura.util import profile
 from Cura.util import version
 from Cura.util import meshLoader
+from Cura.util import resources
 
 class mainWindow(wx.Frame):
        def __init__(self):
@@ -165,9 +167,14 @@ class mainWindow(wx.Frame):
                self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
                self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
 
+               self.youmagineButton = wx.BitmapButton(self.leftPane, -1, wx.Bitmap(resources.getPathForImage('youmagine-text.png')))
+               self.youmagineButton.SetToolTipString("Share your design to YouMagine.com")
+               self.youmagineButton.Bind(wx.EVT_BUTTON, self.OnYouMagine)
+
                self.leftSizer = wx.BoxSizer(wx.VERTICAL)
-               self.leftSizer.Add(self.simpleSettingsPanel)
+               self.leftSizer.Add(self.simpleSettingsPanel, 1)
                self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
+               self.leftSizer.Add(self.youmagineButton, 0, wx.ALIGN_CENTER)
                self.leftPane.SetSizer(self.leftSizer)
                
                #Preview window
@@ -248,6 +255,7 @@ class mainWindow(wx.Frame):
                        
                        # Change location of sash to width of quick mode pane 
                        (width, height) = self.simpleSettingsPanel.GetSizer().GetSize() 
+                       (width, height) = self.youmagineButton.GetSize()
                        self.splitter.SetSashPosition(width, True)
                        
                        # Disable sash
@@ -257,7 +265,7 @@ class mainWindow(wx.Frame):
                        # Enabled sash
                        self.splitter.SetSashSize(4)
                self.scene.updateProfileToControls()
-                                                               
+
        def OnPreferences(self, e):
                prefDialog = preferencesDialog.preferencesDialog(self)
                prefDialog.Centre()
@@ -356,11 +364,6 @@ class mainWindow(wx.Frame):
                        profile.resetProfile()
                        self.updateProfileToControls()
 
-       def OnBatchRun(self, e):
-               br = batchRun.batchRunWindow(self)
-               br.Centre()
-               br.Show(True)
-
        def OnSimpleSwitch(self, e):
                profile.putPreference('startMode', 'Simple')
                self.updateSliceMode()
@@ -409,6 +412,12 @@ class mainWindow(wx.Frame):
                debugger.Centre()
                debugger.Show(True)
 
+       def OnYouMagine(self, e):
+               if len(self.scene._scene.objects()) < 1:
+                       wx.MessageBox('You cannot upload to YouMagine without have a file loaded.', 'Cura', wx.OK | wx.ICON_ERROR)
+                       return
+               youmagineGui.youmagineManager(self, self.scene._scene)
+
        def OnCheckForUpdate(self, e):
                newVersion = version.checkForNewerVersion()
                if newVersion is not None:
diff --git a/Cura/gui/tools/youmagineGui.py b/Cura/gui/tools/youmagineGui.py
new file mode 100644 (file)
index 0000000..7c2cd62
--- /dev/null
@@ -0,0 +1,413 @@
+from __future__ import absolute_import
+__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
+
+import wx
+import threading
+import time
+import re
+import os
+import types
+import webbrowser
+import cStringIO as StringIO
+
+from Cura.util import profile
+from Cura.util import youmagine
+from Cura.util.meshLoaders import stl
+from Cura.util.meshLoaders import amf
+from Cura.util.resources import getPathForImage
+
+from Cura.gui.util import webcam
+
+def getClipboardText():
+       ret = ''
+       try:
+               if not wx.TheClipboard.IsOpened():
+                       wx.TheClipboard.Open()
+                       do = wx.TextDataObject()
+                       if wx.TheClipboard.GetData(do):
+                               ret = do.GetText()
+                       wx.TheClipboard.Close()
+               return ret
+       except:
+               return ret
+
+class youmagineManager(object):
+       def __init__(self, parent, objectScene):
+               self._mainWindow = parent
+               self._scene = objectScene
+               self._ym = youmagine.Youmagine(profile.getPreference('youmagine_token'))
+
+               self._indicatorWindow = workingIndicatorWindow(self._mainWindow)
+               self._getAuthorizationWindow = getAuthorizationWindow(self._mainWindow, self._ym)
+               self._newDesignWindow = newDesignWindow(self._mainWindow, self, self._ym)
+
+               thread = threading.Thread(target=self.checkAuthorizationThread)
+               thread.daemon = True
+               thread.start()
+
+       #Do all the youmagine communication in a background thread, because it can take a while and block the UI thread otherwise
+       def checkAuthorizationThread(self):
+               wx.CallAfter(self._indicatorWindow.showBusy, 'Checking token')
+               if not self._ym.isAuthorized():
+                       wx.CallAfter(self._indicatorWindow.Hide)
+                       if not self._ym.isHostReachable():
+                               wx.MessageBox('Failed to contact YouMagine.com', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
+                               return
+                       wx.CallAfter(self._getAuthorizationWindow.Show)
+                       lastTriedClipboard = ''
+                       while not self._ym.isAuthorized():
+                               time.sleep(0.1)
+                               if self._getAuthorizationWindow.abort:
+                                       wx.CallAfter(self._getAuthorizationWindow.Destroy)
+                                       return
+                               clipboard = getClipboardText()
+                               if len(clipboard) == 20:
+                                       if clipboard != lastTriedClipboard and re.match('[a-zA-Z0-9]*', clipboard):
+                                               lastTriedClipboard = clipboard
+                                               self._ym.setAuthToken(clipboard)
+                       profile.putPreference('youmagine_token', self._ym.getAuthToken())
+                       wx.CallAfter(self._getAuthorizationWindow.Hide)
+                       wx.CallAfter(self._getAuthorizationWindow.Destroy)
+                       wx.MessageBox('Cura is now authorized to share on YouMagine', 'YouMagine.', wx.OK | wx.ICON_INFORMATION)
+               wx.CallAfter(self._indicatorWindow.Hide)
+
+               #TODO: Would you like to create a new design or add the model to an existing design?
+               wx.CallAfter(self._newDesignWindow.Show)
+
+       def createNewDesign(self, name, description, category, license, imageList, publish):
+               thread = threading.Thread(target=self.createNewDesignThread, args=(name, description, category, license, imageList, publish))
+               thread.daemon = True
+               thread.start()
+
+       def createNewDesignThread(self, name, description, category, license, imageList, publish):
+               wx.CallAfter(self._indicatorWindow.showBusy, 'Creating new design on YouMagine...')
+               id = self._ym.createDesign(name, description, category, license)
+               wx.CallAfter(self._indicatorWindow.Hide)
+               if id is None:
+                       wx.MessageBox('Failed to create a design, nothing uploaded!', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
+                       return
+
+               for obj in self._scene.objects():
+                       wx.CallAfter(self._indicatorWindow.showBusy, 'Building model %s...' % (obj.getName()))
+                       time.sleep(0.1)
+                       s = StringIO.StringIO()
+                       filename = obj.getName()
+                       if obj.canStoreAsSTL():
+                               stl.saveSceneStream(s, [obj])
+                               filename += '.stl'
+                       else:
+                               amf.saveSceneStream(s, filename, [obj])
+                               filename += '.amf'
+
+                       wx.CallAfter(self._indicatorWindow.showBusy, 'Uploading model %s...' % (filename))
+                       if self._ym.createDocument(id, filename, s.getvalue()) is None:
+                               wx.MessageBox('Failed to upload %s!' % (filename), 'YouMagine error.', wx.OK | wx.ICON_ERROR)
+                       s.close()
+
+               for image in imageList:
+                       if type(image) in types.StringTypes:
+                               filename = os.path.basename(image)
+                               wx.CallAfter(self._indicatorWindow.showBusy, 'Uploading image %s...' % (filename))
+                               with open(image, "rb") as f:
+                                       if self._ym.createImage(id, filename, f.read()) is None:
+                                               wx.MessageBox('Failed to upload %s!' % (filename), 'YouMagine error.', wx.OK | wx.ICON_ERROR)
+                       elif type(image) is wx.Bitmap:
+                               s = StringIO.StringIO()
+                               if wx.ImageFromBitmap(image).SaveStream(s, wx.BITMAP_TYPE_JPEG):
+                                       if self._ym.createImage(id, "snapshot.jpg", s.getvalue()) is None:
+                                               wx.MessageBox('Failed to upload snapshot!', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
+                       else:
+                               print type(image)
+
+               if publish:
+                       wx.CallAfter(self._indicatorWindow.showBusy, 'Publishing design...')
+                       self._ym.publishDesign(id)
+               wx.CallAfter(self._indicatorWindow.Hide)
+               webbrowser.open(self._ym.viewUrlForDesign(id))
+
+
+class workingIndicatorWindow(wx.Frame):
+       def __init__(self, parent):
+               super(workingIndicatorWindow, self).__init__(parent, title='YouMagine', style=wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.CAPTION)
+               self._panel = wx.Panel(self)
+               self.SetSizer(wx.BoxSizer())
+               self.GetSizer().Add(self._panel, 1, wx.EXPAND)
+
+               self._busyBitmaps = [
+                       wx.Bitmap(getPathForImage('busy-0.png')),
+                       wx.Bitmap(getPathForImage('busy-1.png')),
+                       wx.Bitmap(getPathForImage('busy-2.png')),
+                       wx.Bitmap(getPathForImage('busy-3.png'))
+               ]
+
+               self._indicatorBitmap = wx.StaticBitmap(self._panel, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
+               self._statusText = wx.StaticText(self._panel, -1, '...')
+
+               self._panel._sizer = wx.GridBagSizer(2, 2)
+               self._panel.SetSizer(self._panel._sizer)
+               self._panel._sizer.Add(self._indicatorBitmap, (0, 0))
+               self._panel._sizer.Add(self._statusText, (0, 1), flag=wx.ALIGN_CENTER_VERTICAL)
+
+               self._busyState = 0
+               self._busyTimer = wx.Timer(self)
+               self.Bind(wx.EVT_TIMER, self._busyUpdate, self._busyTimer)
+               self._busyTimer.Start(100)
+
+       def _busyUpdate(self, e):
+               if self._busyState is None:
+                       return
+               self._busyState += 1
+               if self._busyState >= len(self._busyBitmaps):
+                       self._busyState = 0
+               self._indicatorBitmap.SetBitmap(self._busyBitmaps[self._busyState])
+
+       def showBusy(self, text):
+               self._statusText.SetLabel(text)
+               self.Fit()
+               self.Centre()
+               self.Show()
+
+class getAuthorizationWindow(wx.Frame):
+       def __init__(self, parent, ym):
+               super(getAuthorizationWindow, self).__init__(parent, title='YouMagine')
+               self._panel = wx.Panel(self)
+               self.SetSizer(wx.BoxSizer())
+               self.GetSizer().Add(self._panel, 1, wx.EXPAND)
+               self._ym = ym
+               self.abort = False
+
+               self._requestButton = wx.Button(self._panel, -1, 'Request authorization from YouMagine')
+               self._authToken = wx.TextCtrl(self._panel, -1, 'Paste token here')
+
+               self._panel._sizer = wx.GridBagSizer(5, 5)
+               self._panel.SetSizer(self._panel._sizer)
+
+               self._panel._sizer.Add(wx.StaticBitmap(self._panel, -1, wx.Bitmap(getPathForImage('youmagine-text.png'))), (0,0), span=(1,4), flag=wx.ALIGN_CENTRE | wx.ALL)
+               self._panel._sizer.Add(wx.StaticText(self._panel, -1, 'To share your designs on YouMagine\nyou need an account on YouMagine.com\nand authorize Cura to access your account.'), (1, 1))
+               self._panel._sizer.Add(self._requestButton, (2, 1), flag=wx.ALL)
+               self._panel._sizer.Add(wx.StaticText(self._panel, -1, 'This will open a browser window where you can\nauthorize Cura to access your YouMagine account.\nYou can revoke access at any time\nfrom YouMagine.com'), (3, 1), flag=wx.ALL)
+               self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (4,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
+               self._panel._sizer.Add(self._authToken, (5, 1), flag=wx.EXPAND | wx.ALL)
+               self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (6,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
+
+               self.Bind(wx.EVT_BUTTON, self.OnRequestAuthorization, self._requestButton)
+               self.Bind(wx.EVT_TEXT, self.OnEnterToken, self._authToken)
+               self.Bind(wx.EVT_CLOSE, self.OnClose)
+
+               self.Fit()
+               self.Centre()
+
+               self._authToken.SetFocus()
+               self._authToken.SelectAll()
+
+       def OnRequestAuthorization(self, e):
+               webbrowser.open(self._ym.getAuthorizationUrl())
+
+       def OnEnterToken(self, e):
+               self._ym.setAuthToken(self._authToken.GetValue())
+
+       def OnClose(self, e):
+               self.abort = True
+
+class newDesignWindow(wx.Frame):
+       def __init__(self, parent, manager, ym):
+               super(newDesignWindow, self).__init__(parent, title='YouMagine')
+               p = wx.Panel(self)
+               self.SetSizer(wx.BoxSizer())
+               self.GetSizer().Add(p, 1, wx.EXPAND)
+               self._manager = manager
+               self._ym = ym
+               self._cam = webcam.webcam()
+
+               categoryOptions = ym.getCategories()
+               licenseOptions = ym.getLicenses()
+               self._designName = wx.TextCtrl(p, -1, 'Design name')
+               self._designDescription = wx.TextCtrl(p, -1, '', size=(1, 150), style = wx.TE_MULTILINE|wx.TE_PROCESS_TAB)
+               self._designLicense = wx.ComboBox(p, -1, licenseOptions[0], choices=licenseOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
+               self._category = wx.ComboBox(p, -1, categoryOptions[-1], choices=categoryOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
+               self._publish = wx.CheckBox(p, -1, 'Publish after upload')
+               self._shareButton = wx.Button(p, -1, 'Upload')
+               self._imageScroll = wx.lib.scrolledpanel.ScrolledPanel(p)
+
+               self._imageScroll.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
+               self._addImageButton = wx.Button(self._imageScroll, -1, 'Add...', size=(70,52))
+               self._imageScroll.GetSizer().Add(self._addImageButton)
+               self._snapshotButton = wx.Button(self._imageScroll, -1, 'Take...', size=(70,52))
+               self._imageScroll.GetSizer().Add(self._snapshotButton)
+               if not self._cam.hasCamera():
+                       self._snapshotButton.Hide()
+               self._imageScroll.Fit()
+               self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
+               self._imageScroll.SetMinSize((20, self._imageScroll.GetSize()[1] + wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y)))
+
+               self._publish.SetValue(True)
+               self._publish.SetToolTipString('Directly publish the design after uploading.\nWithout this check the design will not be public\nuntil you publish it yourself on YouMagine.com')
+
+               s = wx.GridBagSizer(5, 5)
+               p.SetSizer(s)
+
+               s.Add(wx.StaticBitmap(p, -1, wx.Bitmap(getPathForImage('youmagine-text.png'))), (0,0), span=(1,6), flag=wx.ALIGN_CENTRE | wx.ALL)
+               s.Add(wx.StaticText(p, -1, 'Design name:'), (1, 1))
+               s.Add(self._designName, (1, 2), span=(1,2), flag=wx.EXPAND|wx.ALL)
+               s.Add(wx.StaticText(p, -1, 'Description:'), (2, 1))
+               s.Add(self._designDescription, (2, 2), span=(1,2), flag=wx.EXPAND|wx.ALL)
+               s.Add(wx.StaticText(p, -1, 'Category:'), (3, 1))
+               s.Add(self._category, (3, 2), span=(1,2), flag=wx.ALL)
+               s.Add(wx.StaticText(p, -1, 'License:'), (4, 1))
+               s.Add(self._designLicense, (4, 2), span=(1,2), flag=wx.ALL)
+               s.Add(wx.StaticLine(p, -1), (5,0), span=(1,6), flag=wx.EXPAND|wx.ALL)
+               s.Add(wx.StaticText(p, -1, 'Images:'), (6, 1))
+               s.Add(self._imageScroll, (6, 2), span=(1, 2), flag=wx.EXPAND|wx.ALL)
+               s.Add(wx.StaticLine(p, -1), (7,0), span=(1,6), flag=wx.EXPAND|wx.ALL)
+               s.Add(self._shareButton, (8, 2), flag=wx.ALL)
+               s.Add(self._publish, (8, 3), flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL)
+
+               s.AddGrowableRow(2)
+               s.AddGrowableCol(3)
+
+               self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
+               self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
+               self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
+
+               self.Fit()
+               self.Centre()
+
+               self._designDescription.SetMinSize((1,1))
+               self._designName.SetFocus()
+               self._designName.SelectAll()
+
+       def OnShare(self, e):
+               if self._designName.GetValue() == '':
+                       wx.MessageBox('The name cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
+                       self._designName.SetFocus()
+                       return
+               if self._designDescription.GetValue() == '':
+                       wx.MessageBox('The description cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
+                       self._designDescription.SetFocus()
+                       return
+               imageList = []
+               for child in self._imageScroll.GetChildren():
+                       if hasattr(child, 'imageFilename'):
+                               imageList.append(child.imageFilename)
+                       if hasattr(child, 'imageData'):
+                               imageList.append(child.imageData)
+               self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._publish.GetValue())
+               self.Destroy()
+
+       def OnAddImage(self, e):
+               dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
+               dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
+               if dlg.ShowModal() == wx.ID_OK:
+                       for filename in dlg.GetPaths():
+                               self._addImage(filename)
+               dlg.Destroy()
+
+       def OnTakeImage(self, e):
+               webcamPhotoWindow(self, self._cam).Show()
+
+       def _addImage(self, image):
+               wxImage = None
+               if type(image) in types.StringTypes:
+                       try:
+                               wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
+                       except:
+                               pass
+               else:
+                       wxImage = wx.ImageFromBitmap(image)
+               if wxImage is None:
+                       return
+
+               width, height = wxImage.GetSize()
+               if width > 70:
+                       height = height*70/width
+                       width = 70
+               if height > 52:
+                       width = width*52/height
+                       height = 52
+               wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
+               wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
+               ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
+               if type(image) in types.StringTypes:
+                       ctrl.imageFilename = image
+               else:
+                       ctrl.imageData = image
+
+               delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
+               self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
+
+               self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
+               self._imageScroll.Layout()
+               self._imageScroll.Refresh()
+               self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
+
+       def OnDeleteImage(self, e):
+               ctrl = e.GetEventObject().GetParent()
+               self._imageScroll.GetSizer().Detach(ctrl)
+               ctrl.Destroy()
+
+               self._imageScroll.Layout()
+               self._imageScroll.Refresh()
+               self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
+
+class webcamPhotoWindow(wx.Frame):
+       def __init__(self, parent, cam):
+               super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
+               p = wx.Panel(self)
+               self.panel = p
+               self.SetSizer(wx.BoxSizer())
+               self.GetSizer().Add(p, 1, wx.EXPAND)
+
+               self._cam = cam
+               self._cam.takeNewImage(False)
+
+               s = wx.GridBagSizer(3, 3)
+               p.SetSizer(s)
+
+               self._preview = wx.Panel(p)
+               self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
+               self._takeImageButton = wx.Button(p, -1, 'Snap image')
+               self._takeImageTimer = wx.Timer(self)
+
+               s.Add(self._takeImageButton, pos=(1, 0))
+               s.Add(self._cameraSelect, pos=(1, 1))
+               s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
+
+               if self._cam.getLastImage() is not None:
+                       self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
+               else:
+                       self._preview.SetMinSize((640, 480))
+
+               self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
+               self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
+               self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
+               self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
+
+               self.Fit()
+               self.Centre()
+
+               self._takeImageTimer.Start(200)
+
+       def OnCameraChange(self, e):
+               self._cam.setActiveCamera(self._cameraSelect.GetSelection())
+
+       def OnTakeImage(self, e):
+               self.GetParent()._addImage(self._cam.getLastImage())
+               self.Destroy()
+
+       def OnTakeImageTimer(self, e):
+               self._cam.takeNewImage(False)
+               self.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._preview.GetBackgroundColour(), wx.SOLID))
+               if self._cam.getLastImage() is not None:
+                       self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
+                       self.panel.Fit()
+                       dc.DrawBitmap(self._cam.getLastImage(), 0, 0)
+               else:
+                       dc.Clear()
index a02e9673c68794109e6f405a52523fd9cc7bafdf..7aad233845d80fb35f12886d8b58b8170b9858db 100644 (file)
@@ -45,40 +45,56 @@ def getFFMPEGpath():
 class webcam(object):
        def __init__(self):
                self._cam = None
-               self._hasCamera = False
+               self._cameraList = None
+               self._activeId = -1
                self._overlayImage = wx.Bitmap(getPathForImage('cura-overlay.png'))
                self._overlayUltimaker = wx.Bitmap(getPathForImage('ultimaker-overlay.png'))
+               self._doTimelapse = False
+               self._bitmap = None
 
                #open the camera and close it to check if we have a camera, then open the camera again when we use it for the
                # first time.
-               self._hasCamera = True
-               self._openCam()
-               if self._cam is not None:
-                       print "detected camera"
-                       del self._cam
-                       self._cam = None
-                       self._hasCamera = True
-               else:
-                       self._hasCamera = False
-
-               self._doTimelapse = False
-               self._bitmap = None
+               cameraList = []
+               tryNext = True
+               self._camId = 0
+               while tryNext:
+                       tryNext = False
+                       self._openCam()
+                       if self._cam is not None:
+                               cameraList.append(self._cam.getdisplayname())
+                               tryNext = True
+                               del self._cam
+                               self._cam = None
+                               self._camId += 1
+               self._camId = 0
+               self._activeId = -1
+               self._cameraList = cameraList
 
        def hasCamera(self):
-               return self._hasCamera
+               return len(self._cameraList) > 0
+
+       def listCameras(self):
+               return self._cameraList
+
+       def setActiveCamera(self, cameraIdx):
+               self._camId = cameraIdx
 
        def _openCam(self):
-               print "open camera"
-               if not self._hasCamera:
+               if self._cameraList is not None and self._camId >= len(self._cameraList):
                        return False
                if self._cam is not None:
-                       return True
+                       if self._activeId != self._camId:
+                               del self._cam
+                               self._cam = None
+                       else:
+                               return True
 
+               self._activeId = self._camId
                if cv is not None:
-                       self._cam = highgui.cvCreateCameraCapture(-1)
+                       self._cam = highgui.cvCreateCameraCapture(self._camId)
                elif win32vidcap is not None:
                        try:
-                               self._cam = win32vidcap.new_Dev(0, False)
+                               self._cam = win32vidcap.new_Dev(self._camId, False)
                        except:
                                pass
                return self._cam is not None
@@ -105,7 +121,7 @@ class webcam(object):
                                tmp.displaycapturepinproperties()
                                self._cam = tmp
 
-       def takeNewImage(self):
+       def takeNewImage(self, withOverlay = True):
                if not self._openCam():
                        return
                if cv is not None:
@@ -117,6 +133,7 @@ class webcam(object):
                        try:
                                wxImage = wx.EmptyImage(width, height)
                                wxImage.SetData(buffer[::-1])
+                               wxImage = wxImage.Mirror()
                                if self._bitmap is not None:
                                        del self._bitmap
                                bitmap = wxImage.ConvertToBitmap()
@@ -125,13 +142,14 @@ class webcam(object):
                        except:
                                pass
 
-               dc = wx.MemoryDC()
-               dc.SelectObject(bitmap)
-               dc.DrawBitmap(self._overlayImage, bitmap.GetWidth() - self._overlayImage.GetWidth() - 5, 5, True)
-               if profile.getPreference('machine_type') == 'ultimaker':
-                       dc.DrawBitmap(self._overlayUltimaker, (bitmap.GetWidth() - self._overlayUltimaker.GetWidth()) / 2,
-                               bitmap.GetHeight() - self._overlayUltimaker.GetHeight() - 5, True)
-               dc.SelectObject(wx.NullBitmap)
+               if withOverlay:
+                       dc = wx.MemoryDC()
+                       dc.SelectObject(bitmap)
+                       dc.DrawBitmap(self._overlayImage, bitmap.GetWidth() - self._overlayImage.GetWidth() - 5, 5, True)
+                       if profile.getPreference('machine_type') == 'ultimaker':
+                               dc.DrawBitmap(self._overlayUltimaker, (bitmap.GetWidth() - self._overlayUltimaker.GetWidth()) / 2,
+                                       bitmap.GetHeight() - self._overlayUltimaker.GetHeight() - 5, True)
+                       dc.SelectObject(wx.NullBitmap)
 
                self._bitmap = bitmap
 
index f1415fa6d65d951dd29f6f4a401264d5fdab49fa..c0910988ce534deb3b8940ab4d957ab800f28638 100644 (file)
@@ -208,6 +208,9 @@ class printableObject(object):
                        ret += oriMesh.split(callback)
                return ret
 
+       def canStoreAsSTL(self):
+               return len(self._meshList) < 2
+
        #getVertexIndexList returns an array of vertexes, and an integer array for each mesh in this object.
        # the integer arrays are indexes into the vertex array for each triangle in the model.
        def getVertexIndexList(self):
index 23e19dedaa980006c59f3f9b922f0ce7d6a4a5de..9030536db3a2032ea27d6eb69d1e193e9525796d 100644 (file)
@@ -80,6 +80,11 @@ def loadScene(filename):
        return ret
 
 def saveScene(filename, objects):
+       f = open(filename, 'wb')
+       saveSceneStream(f, filename, objects)
+       f.close()
+
+def saveSceneStream(s, filename, objects):
        xml = StringIO.StringIO()
        xml.write('<?xml version="1.0" encoding="utf-8"?>\n')
        xml.write('<amf unit="millimeter" version="1.1">\n')
@@ -137,7 +142,7 @@ def saveScene(filename, objects):
                xml.write('  </material>\n')
        xml.write('</amf>\n')
 
-       zfile = zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED)
+       zfile = zipfile.ZipFile(s, "w", zipfile.ZIP_DEFLATED)
        zfile.writestr(os.path.basename(filename), xml.getvalue())
        zfile.close()
        xml.close()
index 199775c877c2956bd2eb1ad8c1ab29d6c78cbf3e..1150c1d1031b7832e4fda51ace5430fecc6acc0c 100644 (file)
@@ -54,8 +54,12 @@ def loadScene(filename):
 
 def saveScene(filename, objects):
        f = open(filename, 'wb')
+       saveSceneStream(f, objects)
+       f.close()
+
+def saveSceneStream(stream, objects):
        #Write the STL binary header. This can contain any info, except for "SOLID" at the start.
-       f.write(("CURA BINARY STL EXPORT. " + time.strftime('%a %d %b %Y %H:%M:%S')).ljust(80, '\000'))
+       stream.write(("CURA BINARY STL EXPORT. " + time.strftime('%a %d %b %Y %H:%M:%S')).ljust(80, '\000'))
 
        vertexCount = 0
        for obj in objects:
@@ -63,7 +67,7 @@ def saveScene(filename, objects):
                        vertexCount += m.vertexCount
 
        #Next follow 4 binary bytes containing the amount of faces, and then the face information.
-       f.write(struct.pack("<I", int(vertexCount / 3)))
+       stream.write(struct.pack("<I", int(vertexCount / 3)))
        for obj in objects:
                for m in obj._meshList:
                        vertexes = m.getTransformedVertexes(True)
@@ -71,9 +75,8 @@ def saveScene(filename, objects):
                                v1 = vertexes[idx]
                                v2 = vertexes[idx+1]
                                v3 = vertexes[idx+2]
-                               f.write(struct.pack("<fff", 0.0, 0.0, 0.0))
-                               f.write(struct.pack("<fff", v1[0], v1[1], v1[2]))
-                               f.write(struct.pack("<fff", v2[0], v2[1], v2[2]))
-                               f.write(struct.pack("<fff", v3[0], v3[1], v3[2]))
-                               f.write(struct.pack("<H", 0))
-       f.close()
+                               stream.write(struct.pack("<fff", 0.0, 0.0, 0.0))
+                               stream.write(struct.pack("<fff", v1[0], v1[1], v1[2]))
+                               stream.write(struct.pack("<fff", v2[0], v2[1], v2[2]))
+                               stream.write(struct.pack("<fff", v3[0], v3[1], v3[2]))
+                               stream.write(struct.pack("<H", 0))
index 4bd1f4ae41f8c0a0442062f555ea0dd479f5c00d..d107a0297170291034a1c850aedaafb6491fd881 100644 (file)
@@ -324,6 +324,7 @@ setting('filament_cost_meter', '0', float, 'preference', 'hidden').setLabel('Cos
 setting('auto_detect_sd', 'True', bool, 'preference', 'hidden').setLabel('Auto detect SD card drive', 'Auto detect the SD card. You can disable this because on some systems external hard-drives or USB sticks are detected as SD card.')
 setting('check_for_updates', 'True', bool, 'preference', 'hidden').setLabel('Check for updates', 'Check for newer versions of Cura on startup')
 setting('submit_slice_information', 'False', bool, 'preference', 'hidden').setLabel('Send usage statistics', 'Submit anonymous usage information to improve next versions of Cura')
+setting('youmagine_token', '', str, 'preference', 'hidden')
 
 setting('extruder_head_size_min_x', '0.0', float, 'preference', 'hidden').setLabel('Head size towards X min (mm)', 'The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 75mm for an Ultimaker if the fan is on the left side.')
 setting('extruder_head_size_min_y', '0.0', float, 'preference', 'hidden').setLabel('Head size towards Y min (mm)', 'The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 18mm for an Ultimaker if the fan is on the left side.')
@@ -331,7 +332,7 @@ setting('extruder_head_size_max_x', '0.0', float, 'preference', 'hidden').setLab
 setting('extruder_head_size_max_y', '0.0', float, 'preference', 'hidden').setLabel('Head size towards Y max (mm)', 'The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 35mm for an Ultimaker if the fan is on the left side.')
 setting('extruder_head_size_height', '0.0', float, 'preference', 'hidden').setLabel('Printer gantry height (mm)', 'The height of the gantry holding up the printer head. If an object is higher then this then you cannot print multiple objects one for one. 60mm for an Ultimaker.')
 
-setting('model_colour', '#7AB645', str, 'preference', 'hidden').setLabel('Model colour')
+setting('model_colour', '#FFC924', str, 'preference', 'hidden').setLabel('Model colour')
 setting('model_colour2', '#CB3030', str, 'preference', 'hidden').setLabel('Model colour (2)')
 setting('model_colour3', '#DDD93C', str, 'preference', 'hidden').setLabel('Model colour (3)')
 setting('model_colour4', '#4550D3', str, 'preference', 'hidden').setLabel('Model colour (4)')
@@ -640,7 +641,9 @@ def calculateEdgeWidth():
        if wallThickness < nozzleSize:
                return wallThickness
 
-       lineCount = int(wallThickness / (nozzleSize + 0.0001))
+       lineCount = int(wallThickness / (nozzleSize - 0.0001))
+       if lineCount == 0:
+               return nozzleSize
        lineWidth = wallThickness / lineCount
        lineWidthAlt = wallThickness / (lineCount + 1)
        if lineWidth > nozzleSize * 1.5:
@@ -656,7 +659,7 @@ def calculateLineCount():
        if wallThickness < nozzleSize:
                return 1
 
-       lineCount = int(wallThickness / (nozzleSize + 0.0001))
+       lineCount = int(wallThickness / (nozzleSize - 0.0001))
        lineWidth = wallThickness / lineCount
        lineWidthAlt = wallThickness / (lineCount + 1)
        if lineWidth > nozzleSize * 1.5:
diff --git a/Cura/util/youmagine.py b/Cura/util/youmagine.py
new file mode 100644 (file)
index 0000000..68b49a2
--- /dev/null
@@ -0,0 +1,216 @@
+from __future__ import absolute_import
+__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
+
+import sys
+import json
+import httplib as httpclient
+import urllib
+import cStringIO as StringIO
+
+class Youmagine(object):
+       def __init__(self, authToken):
+               self._hostUrl = ''
+               self._viewUrl = ''
+               self._authUrl = ''
+               self._authToken = authToken
+               self._userName = None
+               self._userID = None
+               self._http = None
+               self._hostReachable = True
+               self._categories = [
+                       ('Art', 2),
+                       ('Fashion', 3),
+                       ('For your home', 4),
+                       ('Gadget', 5),
+                       ('Games', 6),
+                       ('Jewelry', 7),
+                       ('Maker/DIY', 8),
+                       ('Miniatures', 9),
+                       ('Other', 1),
+               ]
+               self._licenses = [
+                       ('Creative Commons - Share Alike', 'cc'),
+                       ('Creative Commons - Attribution-NonCommerical-ShareAlike', 'ccnc'),
+                       ('GPLv3', 'gplv3'),
+               ]
+
+       def getAuthorizationUrl(self):
+               return self._authUrl
+
+       def getCategories(self):
+               return map(lambda n: n[0], self._categories)
+
+       def getLicenses(self):
+               return map(lambda n: n[0], self._licenses)
+
+       def setAuthToken(self, token):
+               self._authToken = token
+               self._userName = None
+               self._userID = None
+
+       def getAuthToken(self):
+               return self._authToken
+
+       def isHostReachable(self):
+               return self._hostReachable
+
+       def viewUrlForDesign(self, id):
+               return 'https://%s/designs/%d' % (self._viewUrl, id)
+
+       def editUrlForDesign(self, id):
+               return 'https://%s/designs/%d/edit' % (self._viewUrl, id)
+
+       def isAuthorized(self):
+               if self._authToken is None:
+                       return False
+               if self._userName is None:
+                       #No username yet, try to request the username to see if the authToken is valid.
+                       result = self._request('GET', '/authorized_integrations/%s/whoami.json' % (self._authToken))
+
+                       if 'error' in result:
+                               self._authToken = None
+                               return False
+                       self._userName = result['screen_name']
+                       self._userID = result['id']
+               return True
+
+       def createDesign(self, name, description, category, license):
+               res = self._request('POST', '/designs.json', {'design[name]': name, 'design[excerpt]': description, 'design[design_category_id]': filter(lambda n: n[0] == category, self._categories)[0][1], 'design[license]': filter(lambda n: n[0] == license, self._licenses)[0][1]})
+               if 'id' in res:
+                       return res['id']
+               print res
+               return None
+
+       def publishDesign(self, id):
+               res = self._request('PUT', '/designs/%d/mark_as/publish.json' % (id), {'ignore': 'me'})
+               if res is not None:
+                       print res
+                       return False
+               return True
+
+       def createDocument(self, designId, name, contents):
+               res = self._request('POST', '/designs/%d/documents.json' % (designId), {'document[name]': name, 'document[description]': 'Uploaded from Cura'}, {'document[file]': (name, contents)})
+               if 'id' in res:
+                       return res['id']
+               print res
+               return None
+
+       def createImage(self, designId, name, contents):
+               res = self._request('POST', '/designs/%d/images.json' % (designId), {'image[name]': name, 'image[description]': 'Uploaded from Cura'}, {'image[file]': (name, contents)})
+               if 'id' in res:
+                       return res['id']
+               print res
+               return None
+
+       def listDesigns(self):
+               res = self._request('GET', '/users/%s/designs.json' % (self._userID))
+               return res
+
+       def _request(self, method, url, postData = None, files = None):
+               retryCount = 2
+               if self._authToken is not None:
+                       url += '?auth_token=%s' % (self._authToken)
+               error = 'Failed to connect to %s' % self._hostUrl
+               for n in xrange(0, retryCount):
+                       if self._http is None:
+                               self._http = httpclient.HTTPSConnection(self._hostUrl)
+                       try:
+                               if files is not None:
+                                       boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T'
+                                       s = StringIO.StringIO()
+                                       for k, v in files.iteritems():
+                                               filename = v[0]
+                                               fileContents = v[1]
+                                               s.write('--%s\r\n' % (boundary))
+                                               s.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (k, filename))
+                                               s.write('Content-Type: application/octet-stream\r\n')
+                                               s.write('Content-Transfer-Encoding: binary\r\n')
+                                               s.write('\r\n')
+                                               s.write(fileContents)
+                                               s.write('\r\n')
+
+                                       for k, v in postData.iteritems():
+                                               s.write('--%s\r\n' % (boundary))
+                                               s.write('Content-Disposition: form-data; name="%s"\r\n' % (k))
+                                               s.write('\r\n')
+                                               s.write(str(v))
+                                               s.write('\r\n')
+                                       s.write('--%s--\r\n' % (boundary))
+
+                                       s = s.getvalue()
+                                       self._http.request(method, url, StringIO.StringIO(s), {"Content-type": "multipart/form-data; boundary=%s" % (boundary), "Content-Length": len(s)})
+                               elif postData is not None:
+                                       self._http.request(method, url, urllib.urlencode(postData), {"Content-type": "application/x-www-form-urlencoded"})
+                               else:
+                                       self._http.request(method, url)
+                       except IOError:
+                               self._http.close()
+                               continue
+                       try:
+                               response = self._http.getresponse()
+                               responseText = response.read()
+                       except:
+                               self._http.close()
+                               continue
+                       try:
+                               if responseText == '':
+                                       return None
+                               return json.loads(responseText)
+                       except ValueError:
+                               print response.getheaders()
+                               error = 'Failed to decode JSON response'
+               self._hostReachable = False
+               return {'error': error}
+
+
+#Fake Youmagine class to test without internet
+class FakeYoumagine(Youmagine):
+       def __init__(self, authToken):
+               super(FakeYoumagine, self).__init__(authToken)
+               self._authUrl = 'file:///C:/Models/output.html'
+               self._authToken = None
+
+       def isAuthorized(self):
+               if self._authToken is None:
+                       return False
+               if self._userName is None:
+                       self._userName = 'FakeYoumagine'
+                       self._userID = '1'
+               return True
+
+       def isHostReachable(self):
+               return True
+
+       def createDesign(self, name, description, category, license):
+               return 1
+
+       def publishDesign(self, id):
+               pass
+
+       def createDocument(self, designId, name, contents):
+               print "Create document: %s" % (name)
+               f = open("C:/models/%s" % (name), "wb")
+               f.write(contents)
+               f.close()
+               return 1
+
+       def createImage(self, designId, name, contents):
+               print "Create image: %s" % (name)
+               f = open("C:/models/%s" % (name), "wb")
+               f.write(contents)
+               f.close()
+               return 1
+
+       def listDesigns(self):
+               return []
+
+       def _request(self, method, url, postData = None, files = None):
+               print "Err: Tried to do request: %s %s" % (method, url)
+
+def main():
+       ym = Youmagine('j3rY9kQF62ptuZF7vqbR')
+       if not ym.isAuthorized():
+               print "Failed to authorize"
+               return
+       for design in ym.listDesigns():
+               print design['name']