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 DESIGN_FILE_EXT = ['.scad', '.blend', '.max', '.stp', '.step', '.igs', '.iges', '.sldasm', '.sldprt', '.skp', '.ipt', '.dwg', '.123d', '.wings']
23 def getClipboardText():
26 if not wx.TheClipboard.IsOpened():
27 wx.TheClipboard.Open()
28 do = wx.TextDataObject()
29 if wx.TheClipboard.GetData(do):
31 wx.TheClipboard.Close()
36 def getAdditionalFiles(objects, onlyExtChanged):
39 name = obj.getOriginFilename()
42 names.add(name[:name.rfind('.')])
45 for ext in DESIGN_FILE_EXT:
46 if os.path.isfile(name + ext):
47 ret.append(name + ext)
53 for ext in DESIGN_FILE_EXT:
54 for filename in os.listdir(os.path.dirname(name)):
55 filename = os.path.join(os.path.dirname(name), filename)
56 if filename.endswith(ext) and filename not in ret and filename not in onlyExtList:
60 class youmagineManager(object):
61 def __init__(self, parent, objectScene):
62 self._mainWindow = parent
63 self._scene = objectScene
64 self._ym = youmagine.Youmagine(profile.getPreference('youmagine_token'), self._progressCallback)
66 self._indicatorWindow = workingIndicatorWindow(self._mainWindow)
67 self._getAuthorizationWindow = getAuthorizationWindow(self._mainWindow, self._ym)
68 self._newDesignWindow = newDesignWindow(self._mainWindow, self, self._ym)
70 thread = threading.Thread(target=self.checkAuthorizationThread)
74 def _progressCallback(self, progress):
75 self._indicatorWindow.progress(progress)
77 #Do all the youmagine communication in a background thread, because it can take a while and block the UI thread otherwise
78 def checkAuthorizationThread(self):
79 wx.CallAfter(self._indicatorWindow.showBusy, _("Checking token"))
80 if not self._ym.isAuthorized():
81 wx.CallAfter(self._indicatorWindow.Hide)
82 if not self._ym.isHostReachable():
83 wx.MessageBox(_("Failed to contact YouMagine.com"), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
85 wx.CallAfter(self._getAuthorizationWindow.Show)
86 lastTriedClipboard = ''
87 while not self._ym.isAuthorized():
89 if self._getAuthorizationWindow.abort:
90 wx.CallAfter(self._getAuthorizationWindow.Destroy)
92 clipboard = getClipboardText()
93 if len(clipboard) == 20:
94 if clipboard != lastTriedClipboard and re.match('[a-zA-Z0-9]*', clipboard):
95 lastTriedClipboard = clipboard
96 self._ym.setAuthToken(clipboard)
97 profile.putPreference('youmagine_token', self._ym.getAuthToken())
98 wx.CallAfter(self._getAuthorizationWindow.Hide)
99 wx.CallAfter(self._getAuthorizationWindow.Destroy)
100 wx.MessageBox(_("Cura is now authorized to share on YouMagine"), _("YouMagine."), wx.OK | wx.ICON_INFORMATION)
101 wx.CallAfter(self._indicatorWindow.Hide)
103 #TODO: Would you like to create a new design or add the model to an existing design?
104 wx.CallAfter(self._newDesignWindow.Show)
106 def createNewDesign(self, name, description, category, license, imageList, extraFileList, publish):
107 thread = threading.Thread(target=self.createNewDesignThread, args=(name, description, category, license, imageList, extraFileList, publish))
111 def createNewDesignThread(self, name, description, category, license, imageList, extraFileList, publish):
112 wx.CallAfter(self._indicatorWindow.showBusy, _("Creating new design on YouMagine..."))
113 id = self._ym.createDesign(name, description, category, license)
114 wx.CallAfter(self._indicatorWindow.Hide)
116 wx.MessageBox(_("Failed to create a design, nothing uploaded!"), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
119 for obj in self._scene.objects():
120 wx.CallAfter(self._indicatorWindow.showBusy, _("Building model %s...") % (obj.getName()))
122 s = StringIO.StringIO()
123 filename = obj.getName()
124 if obj.canStoreAsSTL():
125 stl.saveSceneStream(s, [obj])
128 amf.saveSceneStream(s, filename, [obj])
131 wx.CallAfter(self._indicatorWindow.showBusy, _("Uploading model %s...") % (filename))
132 if self._ym.createDocument(id, filename, s.getvalue()) is None:
133 wx.MessageBox(_("Failed to upload %s!") % (filename), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
136 for extra in extraFileList:
137 wx.CallAfter(self._indicatorWindow.showBusy, _("Uploading file %s...") % (os.path.basename(extra)))
138 with open(extra, "rb") as f:
139 if self._ym.createDocument(id, os.path.basename(extra), f.read()) is None:
140 wx.MessageBox(_("Failed to upload %s!") % (os.path.basename(extra)), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
142 for image in imageList:
143 if type(image) in types.StringTypes:
144 filename = os.path.basename(image)
145 wx.CallAfter(self._indicatorWindow.showBusy, _("Uploading image %s...") % (filename))
146 with open(image, "rb") as f:
147 if self._ym.createImage(id, filename, f.read()) is None:
148 wx.MessageBox(_("Failed to upload %s!") % (filename), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
149 elif type(image) is wx.Bitmap:
150 s = StringIO.StringIO()
151 if wx.ImageFromBitmap(image).SaveStream(s, wx.BITMAP_TYPE_JPEG):
152 if self._ym.createImage(id, "snapshot.jpg", s.getvalue()) is None:
153 wx.MessageBox(_("Failed to upload snapshot!"), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
158 wx.CallAfter(self._indicatorWindow.showBusy, _("Publishing design..."))
159 if not self._ym.publishDesign(id):
160 #If publishing failed try again after 1 second, this might help when you need to wait for the renderer. But does not always work.
162 self._ym.publishDesign(id)
163 wx.CallAfter(self._indicatorWindow.Hide)
165 webbrowser.open(self._ym.viewUrlForDesign(id))
168 class workingIndicatorWindow(wx.Frame):
169 def __init__(self, parent):
170 super(workingIndicatorWindow, self).__init__(parent, title='YouMagine', style=wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.CAPTION)
171 self._panel = wx.Panel(self)
172 self.SetSizer(wx.BoxSizer())
173 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
175 self._busyBitmaps = [
176 wx.Bitmap(getPathForImage('busy-0.png')),
177 wx.Bitmap(getPathForImage('busy-1.png')),
178 wx.Bitmap(getPathForImage('busy-2.png')),
179 wx.Bitmap(getPathForImage('busy-3.png'))
182 self._indicatorBitmap = wx.StaticBitmap(self._panel, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
183 self._statusText = wx.StaticText(self._panel, -1, '...')
184 self._progress = wx.Gauge(self._panel, -1)
185 self._progress.SetRange(1000)
186 self._progress.SetMinSize((250, 30))
188 self._panel._sizer = wx.GridBagSizer(2, 2)
189 self._panel.SetSizer(self._panel._sizer)
190 self._panel._sizer.Add(self._indicatorBitmap, (0, 0), flag=wx.ALL, border=5)
191 self._panel._sizer.Add(self._statusText, (0, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL, border=5)
192 self._panel._sizer.Add(self._progress, (1, 0), span=(1,2), flag=wx.EXPAND|wx.ALL, border=5)
195 self._busyTimer = wx.Timer(self)
196 self.Bind(wx.EVT_TIMER, self._busyUpdate, self._busyTimer)
197 self._busyTimer.Start(100)
199 def _busyUpdate(self, e):
200 if self._busyState is None:
203 if self._busyState >= len(self._busyBitmaps):
205 self._indicatorBitmap.SetBitmap(self._busyBitmaps[self._busyState])
207 def progress(self, progressAmount):
208 wx.CallAfter(self._progress.Show)
209 wx.CallAfter(self._progress.SetValue, progressAmount*1000)
210 wx.CallAfter(self.Layout)
211 wx.CallAfter(self.Fit)
213 def showBusy(self, text):
214 self._statusText.SetLabel(text)
215 self._progress.Hide()
221 class getAuthorizationWindow(wx.Frame):
222 def __init__(self, parent, ym):
223 super(getAuthorizationWindow, self).__init__(parent, title='YouMagine')
224 self._panel = wx.Panel(self)
225 self.SetSizer(wx.BoxSizer())
226 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
230 self._requestButton = wx.Button(self._panel, -1, _("Request authorization from YouMagine"))
231 self._authToken = wx.TextCtrl(self._panel, -1, _("Paste token here"))
233 self._panel._sizer = wx.GridBagSizer(5, 5)
234 self._panel.SetSizer(self._panel._sizer)
236 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, border=5)
237 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))
238 self._panel._sizer.Add(self._requestButton, (2, 1), flag=wx.ALL)
239 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)
240 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (4,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
241 self._panel._sizer.Add(self._authToken, (5, 1), flag=wx.EXPAND | wx.ALL)
242 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (6,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
244 self.Bind(wx.EVT_BUTTON, self.OnRequestAuthorization, self._requestButton)
245 self.Bind(wx.EVT_TEXT, self.OnEnterToken, self._authToken)
246 self.Bind(wx.EVT_CLOSE, self.OnClose)
251 self._authToken.SetFocus()
252 self._authToken.SelectAll()
254 def OnRequestAuthorization(self, e):
255 webbrowser.open(self._ym.getAuthorizationUrl())
257 def OnEnterToken(self, e):
258 self._ym.setAuthToken(self._authToken.GetValue())
260 def OnClose(self, e):
263 class newDesignWindow(wx.Frame):
264 def __init__(self, parent, manager, ym):
265 super(newDesignWindow, self).__init__(parent, title='YouMagine')
267 self.SetSizer(wx.BoxSizer())
268 self.GetSizer().Add(p, 1, wx.EXPAND)
269 self._manager = manager
272 categoryOptions = ym.getCategories()
273 licenseOptions = ym.getLicenses()
274 self._designName = wx.TextCtrl(p, -1, _("Design name"))
275 self._designDescription = wx.TextCtrl(p, -1, '', size=(1, 150), style = wx.TE_MULTILINE)
276 self._designLicense = wx.ComboBox(p, -1, licenseOptions[0], choices=licenseOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
277 self._category = wx.ComboBox(p, -1, categoryOptions[-1], choices=categoryOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
278 self._publish = wx.CheckBox(p, -1, _("Publish after upload"))
279 self._shareButton = wx.Button(p, -1, _("Upload"))
280 self._imageScroll = wx.lib.scrolledpanel.ScrolledPanel(p)
281 self._additionalFiles = wx.CheckListBox(p, -1)
282 self._additionalFiles.InsertItems(getAdditionalFiles(self._manager._scene.objects(), True), 0)
283 self._additionalFiles.SetChecked(range(0, self._additionalFiles.GetCount()))
284 self._additionalFiles.InsertItems(getAdditionalFiles(self._manager._scene.objects(), False), self._additionalFiles.GetCount())
286 self._imageScroll.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
287 self._addImageButton = wx.Button(self._imageScroll, -1, _("Add..."), size=(70,52))
288 self._imageScroll.GetSizer().Add(self._addImageButton)
289 self._snapshotButton = wx.Button(self._imageScroll, -1, _("Webcam..."), size=(70,52))
290 self._imageScroll.GetSizer().Add(self._snapshotButton)
291 self._imageScroll.Fit()
292 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
293 self._imageScroll.SetMinSize((20, self._imageScroll.GetSize()[1] + wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y)))
295 self._publish.SetValue(True)
296 self._publish.SetToolTipString(
297 _("Directly publish the design after uploading.\nWithout this check the design will not be public\nuntil you publish it yourself on YouMagine.com"))
299 s = wx.GridBagSizer(5, 5)
302 s.Add(wx.StaticBitmap(p, -1, wx.Bitmap(getPathForImage('youmagine-text.png'))), (0,0), span=(1,3), flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
303 s.Add(wx.StaticText(p, -1, _("Design name:")), (1, 0), flag=wx.LEFT|wx.TOP, border=5)
304 s.Add(self._designName, (1, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
305 s.Add(wx.StaticText(p, -1, _("Description:")), (2, 0), flag=wx.LEFT|wx.TOP, border=5)
306 s.Add(self._designDescription, (2, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
307 s.Add(wx.StaticText(p, -1, _("Category:")), (3, 0), flag=wx.LEFT|wx.TOP, border=5)
308 s.Add(self._category, (3, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
309 s.Add(wx.StaticText(p, -1, _("License:")), (4, 0), flag=wx.LEFT|wx.TOP, border=5)
310 s.Add(self._designLicense, (4, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
311 s.Add(wx.StaticLine(p, -1), (5,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
312 s.Add(wx.StaticText(p, -1, _("Images:")), (6, 0), flag=wx.LEFT|wx.TOP, border=5)
313 s.Add(self._imageScroll, (6, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
314 s.Add(wx.StaticLine(p, -1), (7,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
315 s.Add(wx.StaticText(p, -1, _("Design files:")), (8, 0), flag=wx.LEFT|wx.TOP, border=5)
316 s.Add(self._additionalFiles, (8, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
317 s.Add(wx.StaticLine(p, -1), (9,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
318 s.Add(self._shareButton, (10, 1), flag=wx.BOTTOM, border=15)
319 s.Add(self._publish, (10, 2), flag=wx.BOTTOM|wx.ALIGN_CENTER_VERTICAL, border=15)
324 self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
325 self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
326 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
331 self._designDescription.SetMinSize((1,1))
332 self._designName.SetFocus()
333 self._designName.SelectAll()
335 def OnShare(self, e):
336 if self._designName.GetValue() == '':
337 wx.MessageBox(_("The name cannot be empty"), _("New design error."), wx.OK | wx.ICON_ERROR)
338 self._designName.SetFocus()
340 if self._designDescription.GetValue() == '':
341 wx.MessageBox(_("The description cannot be empty"), _("New design error."), wx.OK | wx.ICON_ERROR)
342 self._designDescription.SetFocus()
345 for child in self._imageScroll.GetChildren():
346 if hasattr(child, 'imageFilename'):
347 imageList.append(child.imageFilename)
348 if hasattr(child, 'imageData'):
349 imageList.append(child.imageData)
350 self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._additionalFiles.GetCheckedStrings(), self._publish.GetValue())
353 def OnAddImage(self, e):
354 dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
355 dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
356 if dlg.ShowModal() == wx.ID_OK:
357 for filename in dlg.GetPaths():
358 self._addImage(filename)
361 def OnTakeImage(self, e):
362 webcamPhotoWindow(self).Show()
364 def _addImage(self, image):
366 if type(image) in types.StringTypes:
368 wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
372 wxImage = wx.ImageFromBitmap(image)
376 width, height = wxImage.GetSize()
378 height = height*70/width
381 width = width*52/height
383 wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
384 wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
385 ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
386 if type(image) in types.StringTypes:
387 ctrl.imageFilename = image
389 ctrl.imageData = image
391 delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
392 self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
394 self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
395 self._imageScroll.Layout()
396 self._imageScroll.Refresh()
397 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
399 def OnDeleteImage(self, e):
400 ctrl = e.GetEventObject().GetParent()
401 self._imageScroll.GetSizer().Detach(ctrl)
404 self._imageScroll.Layout()
405 self._imageScroll.Refresh()
406 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
408 class webcamPhotoWindow(wx.Frame):
409 def __init__(self, parent):
410 super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
413 self.SetSizer(wx.BoxSizer())
414 self.GetSizer().Add(p, 1, wx.EXPAND)
416 self._cam = webcam.webcam()
417 self._cam.takeNewImage(False)
419 s = wx.GridBagSizer(3, 3)
422 self._preview = wx.Panel(p)
423 self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
424 self._takeImageButton = wx.Button(p, -1, 'Snap image')
425 self._takeImageTimer = wx.Timer(self)
427 s.Add(self._takeImageButton, pos=(1, 0), flag=wx.ALL, border=5)
428 s.Add(self._cameraSelect, pos=(1, 1), flag=wx.ALL, border=5)
429 s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND|wx.ALL, border=5)
431 if self._cam.getLastImage() is not None:
432 self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
434 self._preview.SetMinSize((640, 480))
436 self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
437 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
438 self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
439 self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
444 self._takeImageTimer.Start(200)
446 def OnCameraChange(self, e):
447 self._cam.setActiveCamera(self._cameraSelect.GetSelection())
449 def OnTakeImage(self, e):
450 self.GetParent()._addImage(self._cam.getLastImage())
453 def OnTakeImageTimer(self, e):
454 self._cam.takeNewImage(False)
457 def OnCameraEraseBackground(self, e):
460 dc = wx.ClientDC(self)
461 rect = self.GetUpdateRegion().GetBox()
462 dc.SetClippingRect(rect)
463 dc.SetBackground(wx.Brush(self._preview.GetBackgroundColour(), wx.SOLID))
464 if self._cam.getLastImage() is not None:
465 self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
467 dc.DrawBitmap(self._cam.getLastImage(), 0, 0)