1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
11 import cStringIO as StringIO
13 from Cura.util import profile
14 from Cura.util import youmagine
15 from Cura.util.meshLoaders import stl
16 from Cura.util.meshLoaders import amf
17 from Cura.util.resources import getPathForImage
19 from Cura.gui.util import webcam
21 def getClipboardText():
24 if not wx.TheClipboard.IsOpened():
25 wx.TheClipboard.Open()
26 do = wx.TextDataObject()
27 if wx.TheClipboard.GetData(do):
29 wx.TheClipboard.Close()
34 class youmagineManager(object):
35 def __init__(self, parent, objectScene):
36 self._mainWindow = parent
37 self._scene = objectScene
38 self._ym = youmagine.Youmagine(profile.getPreference('youmagine_token'), self._progressCallback)
40 self._indicatorWindow = workingIndicatorWindow(self._mainWindow)
41 self._getAuthorizationWindow = getAuthorizationWindow(self._mainWindow, self._ym)
42 self._newDesignWindow = newDesignWindow(self._mainWindow, self, self._ym)
44 thread = threading.Thread(target=self.checkAuthorizationThread)
48 def _progressCallback(self, progress):
49 self._indicatorWindow.progress(progress)
51 #Do all the youmagine communication in a background thread, because it can take a while and block the UI thread otherwise
52 def checkAuthorizationThread(self):
53 wx.CallAfter(self._indicatorWindow.showBusy, 'Checking token')
54 if not self._ym.isAuthorized():
55 wx.CallAfter(self._indicatorWindow.Hide)
56 if not self._ym.isHostReachable():
57 wx.MessageBox('Failed to contact YouMagine.com', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
59 wx.CallAfter(self._getAuthorizationWindow.Show)
60 lastTriedClipboard = ''
61 while not self._ym.isAuthorized():
63 if self._getAuthorizationWindow.abort:
64 wx.CallAfter(self._getAuthorizationWindow.Destroy)
66 clipboard = getClipboardText()
67 if len(clipboard) == 20:
68 if clipboard != lastTriedClipboard and re.match('[a-zA-Z0-9]*', clipboard):
69 lastTriedClipboard = clipboard
70 self._ym.setAuthToken(clipboard)
71 profile.putPreference('youmagine_token', self._ym.getAuthToken())
72 wx.CallAfter(self._getAuthorizationWindow.Hide)
73 wx.CallAfter(self._getAuthorizationWindow.Destroy)
74 wx.MessageBox('Cura is now authorized to share on YouMagine', 'YouMagine.', wx.OK | wx.ICON_INFORMATION)
75 wx.CallAfter(self._indicatorWindow.Hide)
77 #TODO: Would you like to create a new design or add the model to an existing design?
78 wx.CallAfter(self._newDesignWindow.Show)
80 def createNewDesign(self, name, description, category, license, imageList, publish):
81 thread = threading.Thread(target=self.createNewDesignThread, args=(name, description, category, license, imageList, publish))
85 def createNewDesignThread(self, name, description, category, license, imageList, publish):
86 wx.CallAfter(self._indicatorWindow.showBusy, 'Creating new design on YouMagine...')
87 id = self._ym.createDesign(name, description, category, license)
88 wx.CallAfter(self._indicatorWindow.Hide)
90 wx.MessageBox('Failed to create a design, nothing uploaded!', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
93 for obj in self._scene.objects():
94 wx.CallAfter(self._indicatorWindow.showBusy, 'Building model %s...' % (obj.getName()))
96 s = StringIO.StringIO()
97 filename = obj.getName()
98 if obj.canStoreAsSTL():
99 stl.saveSceneStream(s, [obj])
102 amf.saveSceneStream(s, filename, [obj])
105 wx.CallAfter(self._indicatorWindow.showBusy, 'Uploading model %s...' % (filename))
106 if self._ym.createDocument(id, filename, s.getvalue()) is None:
107 wx.MessageBox('Failed to upload %s!' % (filename), 'YouMagine error.', wx.OK | wx.ICON_ERROR)
110 for image in imageList:
111 if type(image) in types.StringTypes:
112 filename = os.path.basename(image)
113 wx.CallAfter(self._indicatorWindow.showBusy, 'Uploading image %s...' % (filename))
114 with open(image, "rb") as f:
115 if self._ym.createImage(id, filename, f.read()) is None:
116 wx.MessageBox('Failed to upload %s!' % (filename), 'YouMagine error.', wx.OK | wx.ICON_ERROR)
117 elif type(image) is wx.Bitmap:
118 s = StringIO.StringIO()
119 if wx.ImageFromBitmap(image).SaveStream(s, wx.BITMAP_TYPE_JPEG):
120 if self._ym.createImage(id, "snapshot.jpg", s.getvalue()) is None:
121 wx.MessageBox('Failed to upload snapshot!', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
126 wx.CallAfter(self._indicatorWindow.showBusy, 'Publishing design...')
127 self._ym.publishDesign(id)
128 wx.CallAfter(self._indicatorWindow.Hide)
129 webbrowser.open(self._ym.viewUrlForDesign(id))
132 class workingIndicatorWindow(wx.Frame):
133 def __init__(self, parent):
134 super(workingIndicatorWindow, self).__init__(parent, title='YouMagine', style=wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.CAPTION)
135 self._panel = wx.Panel(self)
136 self.SetSizer(wx.BoxSizer())
137 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
139 self._busyBitmaps = [
140 wx.Bitmap(getPathForImage('busy-0.png')),
141 wx.Bitmap(getPathForImage('busy-1.png')),
142 wx.Bitmap(getPathForImage('busy-2.png')),
143 wx.Bitmap(getPathForImage('busy-3.png'))
146 self._indicatorBitmap = wx.StaticBitmap(self._panel, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
147 self._statusText = wx.StaticText(self._panel, -1, '...')
148 self._progress = wx.Gauge(self._panel, -1)
149 self._progress.SetRange(1000)
150 self._progress.SetMinSize((250, 30))
152 self._panel._sizer = wx.GridBagSizer(2, 2)
153 self._panel.SetSizer(self._panel._sizer)
154 self._panel._sizer.Add(self._indicatorBitmap, (0, 0))
155 self._panel._sizer.Add(self._statusText, (0, 1), flag=wx.ALIGN_CENTER_VERTICAL)
156 self._panel._sizer.Add(self._progress, (1, 0), span=(1,2), flag=wx.EXPAND)
159 self._busyTimer = wx.Timer(self)
160 self.Bind(wx.EVT_TIMER, self._busyUpdate, self._busyTimer)
161 self._busyTimer.Start(100)
163 def _busyUpdate(self, e):
164 if self._busyState is None:
167 if self._busyState >= len(self._busyBitmaps):
169 self._indicatorBitmap.SetBitmap(self._busyBitmaps[self._busyState])
171 def progress(self, progressAmount):
172 wx.CallAfter(self._progress.Show)
173 wx.CallAfter(self._progress.SetValue, progressAmount*1000)
174 wx.CallAfter(self.Layout)
175 wx.CallAfter(self.Fit)
177 def showBusy(self, text):
178 self._statusText.SetLabel(text)
179 self._progress.Hide()
185 class getAuthorizationWindow(wx.Frame):
186 def __init__(self, parent, ym):
187 super(getAuthorizationWindow, self).__init__(parent, title='YouMagine')
188 self._panel = wx.Panel(self)
189 self.SetSizer(wx.BoxSizer())
190 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
194 self._requestButton = wx.Button(self._panel, -1, 'Request authorization from YouMagine')
195 self._authToken = wx.TextCtrl(self._panel, -1, 'Paste token here')
197 self._panel._sizer = wx.GridBagSizer(5, 5)
198 self._panel.SetSizer(self._panel._sizer)
200 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)
201 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))
202 self._panel._sizer.Add(self._requestButton, (2, 1), flag=wx.ALL)
203 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)
204 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (4,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
205 self._panel._sizer.Add(self._authToken, (5, 1), flag=wx.EXPAND | wx.ALL)
206 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (6,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
208 self.Bind(wx.EVT_BUTTON, self.OnRequestAuthorization, self._requestButton)
209 self.Bind(wx.EVT_TEXT, self.OnEnterToken, self._authToken)
210 self.Bind(wx.EVT_CLOSE, self.OnClose)
215 self._authToken.SetFocus()
216 self._authToken.SelectAll()
218 def OnRequestAuthorization(self, e):
219 webbrowser.open(self._ym.getAuthorizationUrl())
221 def OnEnterToken(self, e):
222 self._ym.setAuthToken(self._authToken.GetValue())
224 def OnClose(self, e):
227 class newDesignWindow(wx.Frame):
228 def __init__(self, parent, manager, ym):
229 super(newDesignWindow, self).__init__(parent, title='YouMagine')
231 self.SetSizer(wx.BoxSizer())
232 self.GetSizer().Add(p, 1, wx.EXPAND)
233 self._manager = manager
235 self._cam = webcam.webcam()
237 categoryOptions = ym.getCategories()
238 licenseOptions = ym.getLicenses()
239 self._designName = wx.TextCtrl(p, -1, 'Design name')
240 self._designDescription = wx.TextCtrl(p, -1, '', size=(1, 150), style = wx.TE_MULTILINE|wx.TE_PROCESS_TAB)
241 self._designLicense = wx.ComboBox(p, -1, licenseOptions[0], choices=licenseOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
242 self._category = wx.ComboBox(p, -1, categoryOptions[-1], choices=categoryOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
243 self._publish = wx.CheckBox(p, -1, 'Publish after upload')
244 self._shareButton = wx.Button(p, -1, 'Upload')
245 self._imageScroll = wx.lib.scrolledpanel.ScrolledPanel(p)
247 self._imageScroll.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
248 self._addImageButton = wx.Button(self._imageScroll, -1, 'Add...', size=(70,52))
249 self._imageScroll.GetSizer().Add(self._addImageButton)
250 self._snapshotButton = wx.Button(self._imageScroll, -1, 'Take...', size=(70,52))
251 self._imageScroll.GetSizer().Add(self._snapshotButton)
252 if not self._cam.hasCamera():
253 self._snapshotButton.Hide()
254 self._imageScroll.Fit()
255 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
256 self._imageScroll.SetMinSize((20, self._imageScroll.GetSize()[1] + wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y)))
258 self._publish.SetValue(True)
259 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')
261 s = wx.GridBagSizer(5, 5)
264 s.Add(wx.StaticBitmap(p, -1, wx.Bitmap(getPathForImage('youmagine-text.png'))), (0,0), span=(1,6), flag=wx.ALIGN_CENTRE | wx.ALL)
265 s.Add(wx.StaticText(p, -1, 'Design name:'), (1, 1))
266 s.Add(self._designName, (1, 2), span=(1,2), flag=wx.EXPAND|wx.ALL)
267 s.Add(wx.StaticText(p, -1, 'Description:'), (2, 1))
268 s.Add(self._designDescription, (2, 2), span=(1,2), flag=wx.EXPAND|wx.ALL)
269 s.Add(wx.StaticText(p, -1, 'Category:'), (3, 1))
270 s.Add(self._category, (3, 2), span=(1,2), flag=wx.ALL)
271 s.Add(wx.StaticText(p, -1, 'License:'), (4, 1))
272 s.Add(self._designLicense, (4, 2), span=(1,2), flag=wx.ALL)
273 s.Add(wx.StaticLine(p, -1), (5,0), span=(1,6), flag=wx.EXPAND|wx.ALL)
274 s.Add(wx.StaticText(p, -1, 'Images:'), (6, 1))
275 s.Add(self._imageScroll, (6, 2), span=(1, 2), flag=wx.EXPAND|wx.ALL)
276 s.Add(wx.StaticLine(p, -1), (7,0), span=(1,6), flag=wx.EXPAND|wx.ALL)
277 s.Add(self._shareButton, (8, 2), flag=wx.ALL)
278 s.Add(self._publish, (8, 3), flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL)
283 self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
284 self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
285 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
290 self._designDescription.SetMinSize((1,1))
291 self._designName.SetFocus()
292 self._designName.SelectAll()
294 def OnShare(self, e):
295 if self._designName.GetValue() == '':
296 wx.MessageBox('The name cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
297 self._designName.SetFocus()
299 if self._designDescription.GetValue() == '':
300 wx.MessageBox('The description cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
301 self._designDescription.SetFocus()
304 for child in self._imageScroll.GetChildren():
305 if hasattr(child, 'imageFilename'):
306 imageList.append(child.imageFilename)
307 if hasattr(child, 'imageData'):
308 imageList.append(child.imageData)
309 self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._publish.GetValue())
312 def OnAddImage(self, e):
313 dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
314 dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
315 if dlg.ShowModal() == wx.ID_OK:
316 for filename in dlg.GetPaths():
317 self._addImage(filename)
320 def OnTakeImage(self, e):
321 webcamPhotoWindow(self, self._cam).Show()
323 def _addImage(self, image):
325 if type(image) in types.StringTypes:
327 wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
331 wxImage = wx.ImageFromBitmap(image)
335 width, height = wxImage.GetSize()
337 height = height*70/width
340 width = width*52/height
342 wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
343 wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
344 ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
345 if type(image) in types.StringTypes:
346 ctrl.imageFilename = image
348 ctrl.imageData = image
350 delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
351 self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
353 self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
354 self._imageScroll.Layout()
355 self._imageScroll.Refresh()
356 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
358 def OnDeleteImage(self, e):
359 ctrl = e.GetEventObject().GetParent()
360 self._imageScroll.GetSizer().Detach(ctrl)
363 self._imageScroll.Layout()
364 self._imageScroll.Refresh()
365 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
367 class webcamPhotoWindow(wx.Frame):
368 def __init__(self, parent, cam):
369 super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
372 self.SetSizer(wx.BoxSizer())
373 self.GetSizer().Add(p, 1, wx.EXPAND)
376 self._cam.takeNewImage(False)
378 s = wx.GridBagSizer(3, 3)
381 self._preview = wx.Panel(p)
382 self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
383 self._takeImageButton = wx.Button(p, -1, 'Snap image')
384 self._takeImageTimer = wx.Timer(self)
386 s.Add(self._takeImageButton, pos=(1, 0))
387 s.Add(self._cameraSelect, pos=(1, 1))
388 s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
390 if self._cam.getLastImage() is not None:
391 self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
393 self._preview.SetMinSize((640, 480))
395 self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
396 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
397 self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
398 self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
403 self._takeImageTimer.Start(200)
405 def OnCameraChange(self, e):
406 self._cam.setActiveCamera(self._cameraSelect.GetSelection())
408 def OnTakeImage(self, e):
409 self.GetParent()._addImage(self._cam.getLastImage())
412 def OnTakeImageTimer(self, e):
413 self._cam.takeNewImage(False)
416 def OnCameraEraseBackground(self, e):
419 dc = wx.ClientDC(self)
420 rect = self.GetUpdateRegion().GetBox()
421 dc.SetClippingRect(rect)
422 dc.SetBackground(wx.Brush(self._preview.GetBackgroundColour(), wx.SOLID))
423 if self._cam.getLastImage() is not None:
424 self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
426 dc.DrawBitmap(self._cam.getLastImage(), 0, 0)