From: daid Date: Mon, 12 Aug 2013 12:54:18 +0000 (+0200) Subject: Add youmagine uploader (inactive, no API urls filled) X-Git-Tag: 13.10~101 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=dae8f5e9813465a1a361ad1af6c4a6e661bf113b;p=cura.git Add youmagine uploader (inactive, no API urls filled) --- diff --git a/Cura/gui/mainWindow.py b/Cura/gui/mainWindow.py index 0ce1827d..e432b8a8 100644 --- a/Cura/gui/mainWindow.py +++ b/Cura/gui/mainWindow.py @@ -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 index 00000000..7c2cd62c --- /dev/null +++ b/Cura/gui/tools/youmagineGui.py @@ -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() diff --git a/Cura/gui/util/webcam.py b/Cura/gui/util/webcam.py index a02e9673..7aad2338 100644 --- a/Cura/gui/util/webcam.py +++ b/Cura/gui/util/webcam.py @@ -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 diff --git a/Cura/util/mesh.py b/Cura/util/mesh.py index f1415fa6..c0910988 100644 --- a/Cura/util/mesh.py +++ b/Cura/util/mesh.py @@ -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): diff --git a/Cura/util/meshLoaders/amf.py b/Cura/util/meshLoaders/amf.py index 23e19ded..9030536d 100644 --- a/Cura/util/meshLoaders/amf.py +++ b/Cura/util/meshLoaders/amf.py @@ -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('\n') xml.write('\n') @@ -137,7 +142,7 @@ def saveScene(filename, objects): xml.write(' \n') xml.write('\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() diff --git a/Cura/util/meshLoaders/stl.py b/Cura/util/meshLoaders/stl.py index 199775c8..1150c1d1 100644 --- a/Cura/util/meshLoaders/stl.py +++ b/Cura/util/meshLoaders/stl.py @@ -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(" 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 index 00000000..68b49a20 --- /dev/null +++ b/Cura/util/youmagine.py @@ -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']