chiark / gitweb /
Upload design files with mesh files when uploading to YouMagine.
[cura.git] / Cura / gui / tools / youmagineGui.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import wx
5 import threading
6 import time
7 import re
8 import os
9 import types
10 import webbrowser
11 import cStringIO as StringIO
12
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
18
19 from Cura.gui.util import webcam
20
21 DESIGN_FILE_EXT = ['.scad', '.blend', '.max', '.stp', '.step', '.igs', '.iges', '.sldasm', '.sldprt', '.skp', '.ipt', '.dwg', '.123d', '.wings']
22
23 def getClipboardText():
24         ret = ''
25         try:
26                 if not wx.TheClipboard.IsOpened():
27                         wx.TheClipboard.Open()
28                         do = wx.TextDataObject()
29                         if wx.TheClipboard.GetData(do):
30                                 ret = do.GetText()
31                         wx.TheClipboard.Close()
32                 return ret
33         except:
34                 return ret
35
36 def getAdditionalFiles(objects, onlyExtChanged):
37         names = set()
38         for obj in objects:
39                 name = obj.getOriginFilename()
40                 if name is None:
41                         continue
42                 names.add(name[:name.rfind('.')])
43         ret = []
44         for name in names:
45                 for ext in DESIGN_FILE_EXT:
46                         if os.path.isfile(name + ext):
47                                 ret.append(name + ext)
48         if onlyExtChanged:
49                 return ret
50         onlyExtList = ret
51         ret = []
52         for name in names:
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:
57                                         ret.append(filename)
58         return ret
59
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)
65
66                 self._indicatorWindow = workingIndicatorWindow(self._mainWindow)
67                 self._getAuthorizationWindow = getAuthorizationWindow(self._mainWindow, self._ym)
68                 self._newDesignWindow = newDesignWindow(self._mainWindow, self, self._ym)
69
70                 thread = threading.Thread(target=self.checkAuthorizationThread)
71                 thread.daemon = True
72                 thread.start()
73
74         def _progressCallback(self, progress):
75                 self._indicatorWindow.progress(progress)
76
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)
84                                 return
85                         wx.CallAfter(self._getAuthorizationWindow.Show)
86                         lastTriedClipboard = ''
87                         while not self._ym.isAuthorized():
88                                 time.sleep(0.1)
89                                 if self._getAuthorizationWindow.abort:
90                                         wx.CallAfter(self._getAuthorizationWindow.Destroy)
91                                         return
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)
102
103                 #TODO: Would you like to create a new design or add the model to an existing design?
104                 wx.CallAfter(self._newDesignWindow.Show)
105
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))
108                 thread.daemon = True
109                 thread.start()
110
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)
115                 if id is None:
116                         wx.MessageBox('Failed to create a design, nothing uploaded!', 'YouMagine error.', wx.OK | wx.ICON_ERROR)
117                         return
118
119                 for obj in self._scene.objects():
120                         wx.CallAfter(self._indicatorWindow.showBusy, 'Building model %s...' % (obj.getName()))
121                         time.sleep(0.1)
122                         s = StringIO.StringIO()
123                         filename = obj.getName()
124                         if obj.canStoreAsSTL():
125                                 stl.saveSceneStream(s, [obj])
126                                 filename += '.stl'
127                         else:
128                                 amf.saveSceneStream(s, filename, [obj])
129                                 filename += '.amf'
130
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)
134                         s.close()
135
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)
141
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)
154                         else:
155                                 print type(image)
156
157                 if publish:
158                         wx.CallAfter(self._indicatorWindow.showBusy, 'Publishing design...')
159                         self._ym.publishDesign(id)
160                 wx.CallAfter(self._indicatorWindow.Hide)
161
162                 webbrowser.open(self._ym.viewUrlForDesign(id))
163
164
165 class workingIndicatorWindow(wx.Frame):
166         def __init__(self, parent):
167                 super(workingIndicatorWindow, self).__init__(parent, title='YouMagine', style=wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.CAPTION)
168                 self._panel = wx.Panel(self)
169                 self.SetSizer(wx.BoxSizer())
170                 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
171
172                 self._busyBitmaps = [
173                         wx.Bitmap(getPathForImage('busy-0.png')),
174                         wx.Bitmap(getPathForImage('busy-1.png')),
175                         wx.Bitmap(getPathForImage('busy-2.png')),
176                         wx.Bitmap(getPathForImage('busy-3.png'))
177                 ]
178
179                 self._indicatorBitmap = wx.StaticBitmap(self._panel, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
180                 self._statusText = wx.StaticText(self._panel, -1, '...')
181                 self._progress = wx.Gauge(self._panel, -1)
182                 self._progress.SetRange(1000)
183                 self._progress.SetMinSize((250, 30))
184
185                 self._panel._sizer = wx.GridBagSizer(2, 2)
186                 self._panel.SetSizer(self._panel._sizer)
187                 self._panel._sizer.Add(self._indicatorBitmap, (0, 0), flag=wx.ALL, border=5)
188                 self._panel._sizer.Add(self._statusText, (0, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL, border=5)
189                 self._panel._sizer.Add(self._progress, (1, 0), span=(1,2), flag=wx.EXPAND|wx.ALL, border=5)
190
191                 self._busyState = 0
192                 self._busyTimer = wx.Timer(self)
193                 self.Bind(wx.EVT_TIMER, self._busyUpdate, self._busyTimer)
194                 self._busyTimer.Start(100)
195
196         def _busyUpdate(self, e):
197                 if self._busyState is None:
198                         return
199                 self._busyState += 1
200                 if self._busyState >= len(self._busyBitmaps):
201                         self._busyState = 0
202                 self._indicatorBitmap.SetBitmap(self._busyBitmaps[self._busyState])
203
204         def progress(self, progressAmount):
205                 wx.CallAfter(self._progress.Show)
206                 wx.CallAfter(self._progress.SetValue, progressAmount*1000)
207                 wx.CallAfter(self.Layout)
208                 wx.CallAfter(self.Fit)
209
210         def showBusy(self, text):
211                 self._statusText.SetLabel(text)
212                 self._progress.Hide()
213                 self.Layout()
214                 self.Fit()
215                 self.Centre()
216                 self.Show()
217
218 class getAuthorizationWindow(wx.Frame):
219         def __init__(self, parent, ym):
220                 super(getAuthorizationWindow, self).__init__(parent, title='YouMagine')
221                 self._panel = wx.Panel(self)
222                 self.SetSizer(wx.BoxSizer())
223                 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
224                 self._ym = ym
225                 self.abort = False
226
227                 self._requestButton = wx.Button(self._panel, -1, 'Request authorization from YouMagine')
228                 self._authToken = wx.TextCtrl(self._panel, -1, 'Paste token here')
229
230                 self._panel._sizer = wx.GridBagSizer(5, 5)
231                 self._panel.SetSizer(self._panel._sizer)
232
233                 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)
234                 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))
235                 self._panel._sizer.Add(self._requestButton, (2, 1), flag=wx.ALL)
236                 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)
237                 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (4,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
238                 self._panel._sizer.Add(self._authToken, (5, 1), flag=wx.EXPAND | wx.ALL)
239                 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (6,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
240
241                 self.Bind(wx.EVT_BUTTON, self.OnRequestAuthorization, self._requestButton)
242                 self.Bind(wx.EVT_TEXT, self.OnEnterToken, self._authToken)
243                 self.Bind(wx.EVT_CLOSE, self.OnClose)
244
245                 self.Fit()
246                 self.Centre()
247
248                 self._authToken.SetFocus()
249                 self._authToken.SelectAll()
250
251         def OnRequestAuthorization(self, e):
252                 webbrowser.open(self._ym.getAuthorizationUrl())
253
254         def OnEnterToken(self, e):
255                 self._ym.setAuthToken(self._authToken.GetValue())
256
257         def OnClose(self, e):
258                 self.abort = True
259
260 class newDesignWindow(wx.Frame):
261         def __init__(self, parent, manager, ym):
262                 super(newDesignWindow, self).__init__(parent, title='YouMagine')
263                 p = wx.Panel(self)
264                 self.SetSizer(wx.BoxSizer())
265                 self.GetSizer().Add(p, 1, wx.EXPAND)
266                 self._manager = manager
267                 self._ym = ym
268
269                 categoryOptions = ym.getCategories()
270                 licenseOptions = ym.getLicenses()
271                 self._designName = wx.TextCtrl(p, -1, 'Design name')
272                 self._designDescription = wx.TextCtrl(p, -1, '', size=(1, 150), style = wx.TE_MULTILINE)
273                 self._designLicense = wx.ComboBox(p, -1, licenseOptions[0], choices=licenseOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
274                 self._category = wx.ComboBox(p, -1, categoryOptions[-1], choices=categoryOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
275                 self._publish = wx.CheckBox(p, -1, 'Publish after upload')
276                 self._shareButton = wx.Button(p, -1, 'Upload')
277                 self._imageScroll = wx.lib.scrolledpanel.ScrolledPanel(p)
278                 self._additionalFiles = wx.CheckListBox(p, -1)
279                 self._additionalFiles.InsertItems(getAdditionalFiles(self._manager._scene.objects(), True), 0)
280                 self._additionalFiles.SetChecked(range(0, self._additionalFiles.GetCount()))
281                 self._additionalFiles.InsertItems(getAdditionalFiles(self._manager._scene.objects(), False), self._additionalFiles.GetCount())
282
283                 self._imageScroll.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
284                 self._addImageButton = wx.Button(self._imageScroll, -1, 'Add...', size=(70,52))
285                 self._imageScroll.GetSizer().Add(self._addImageButton)
286                 self._snapshotButton = wx.Button(self._imageScroll, -1, 'Webcam...', size=(70,52))
287                 self._imageScroll.GetSizer().Add(self._snapshotButton)
288                 self._imageScroll.Fit()
289                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
290                 self._imageScroll.SetMinSize((20, self._imageScroll.GetSize()[1] + wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y)))
291
292                 self._publish.SetValue(True)
293                 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')
294
295                 s = wx.GridBagSizer(5, 5)
296                 p.SetSizer(s)
297
298                 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)
299                 s.Add(wx.StaticText(p, -1, 'Design name:'), (1, 0), flag=wx.LEFT|wx.TOP, border=5)
300                 s.Add(self._designName, (1, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
301                 s.Add(wx.StaticText(p, -1, 'Description:'), (2, 0), flag=wx.LEFT|wx.TOP, border=5)
302                 s.Add(self._designDescription, (2, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
303                 s.Add(wx.StaticText(p, -1, 'Category:'), (3, 0), flag=wx.LEFT|wx.TOP, border=5)
304                 s.Add(self._category, (3, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
305                 s.Add(wx.StaticText(p, -1, 'License:'), (4, 0), flag=wx.LEFT|wx.TOP, border=5)
306                 s.Add(self._designLicense, (4, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
307                 s.Add(wx.StaticLine(p, -1), (5,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
308                 s.Add(wx.StaticText(p, -1, 'Images:'), (6, 0), flag=wx.LEFT|wx.TOP, border=5)
309                 s.Add(self._imageScroll, (6, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
310                 s.Add(wx.StaticLine(p, -1), (7,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
311                 s.Add(wx.StaticText(p, -1, 'Design files:'), (8, 0), flag=wx.LEFT|wx.TOP, border=5)
312                 s.Add(self._additionalFiles, (8, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
313                 s.Add(wx.StaticLine(p, -1), (9,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
314                 s.Add(self._shareButton, (10, 1), flag=wx.BOTTOM, border=15)
315                 s.Add(self._publish, (10, 2), flag=wx.BOTTOM|wx.ALIGN_CENTER_VERTICAL, border=15)
316
317                 s.AddGrowableRow(2)
318                 s.AddGrowableCol(2)
319
320                 self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
321                 self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
322                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
323
324                 self.Fit()
325                 self.Centre()
326
327                 self._designDescription.SetMinSize((1,1))
328                 self._designName.SetFocus()
329                 self._designName.SelectAll()
330
331         def OnShare(self, e):
332                 if self._designName.GetValue() == '':
333                         wx.MessageBox('The name cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
334                         self._designName.SetFocus()
335                         return
336                 if self._designDescription.GetValue() == '':
337                         wx.MessageBox('The description cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
338                         self._designDescription.SetFocus()
339                         return
340                 imageList = []
341                 for child in self._imageScroll.GetChildren():
342                         if hasattr(child, 'imageFilename'):
343                                 imageList.append(child.imageFilename)
344                         if hasattr(child, 'imageData'):
345                                 imageList.append(child.imageData)
346                 self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._additionalFiles.GetCheckedStrings(), self._publish.GetValue())
347                 self.Destroy()
348
349         def OnAddImage(self, e):
350                 dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
351                 dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
352                 if dlg.ShowModal() == wx.ID_OK:
353                         for filename in dlg.GetPaths():
354                                 self._addImage(filename)
355                 dlg.Destroy()
356
357         def OnTakeImage(self, e):
358                 webcamPhotoWindow(self).Show()
359
360         def _addImage(self, image):
361                 wxImage = None
362                 if type(image) in types.StringTypes:
363                         try:
364                                 wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
365                         except:
366                                 pass
367                 else:
368                         wxImage = wx.ImageFromBitmap(image)
369                 if wxImage is None:
370                         return
371
372                 width, height = wxImage.GetSize()
373                 if width > 70:
374                         height = height*70/width
375                         width = 70
376                 if height > 52:
377                         width = width*52/height
378                         height = 52
379                 wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
380                 wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
381                 ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
382                 if type(image) in types.StringTypes:
383                         ctrl.imageFilename = image
384                 else:
385                         ctrl.imageData = image
386
387                 delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
388                 self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
389
390                 self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
391                 self._imageScroll.Layout()
392                 self._imageScroll.Refresh()
393                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
394
395         def OnDeleteImage(self, e):
396                 ctrl = e.GetEventObject().GetParent()
397                 self._imageScroll.GetSizer().Detach(ctrl)
398                 ctrl.Destroy()
399
400                 self._imageScroll.Layout()
401                 self._imageScroll.Refresh()
402                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
403
404 class webcamPhotoWindow(wx.Frame):
405         def __init__(self, parent):
406                 super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
407                 p = wx.Panel(self)
408                 self.panel = p
409                 self.SetSizer(wx.BoxSizer())
410                 self.GetSizer().Add(p, 1, wx.EXPAND)
411
412                 self._cam = webcam.webcam()
413                 self._cam.takeNewImage(False)
414
415                 s = wx.GridBagSizer(3, 3)
416                 p.SetSizer(s)
417
418                 self._preview = wx.Panel(p)
419                 self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
420                 self._takeImageButton = wx.Button(p, -1, 'Snap image')
421                 self._takeImageTimer = wx.Timer(self)
422
423                 s.Add(self._takeImageButton, pos=(1, 0), flag=wx.ALL, border=5)
424                 s.Add(self._cameraSelect, pos=(1, 1), flag=wx.ALL, border=5)
425                 s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND|wx.ALL, border=5)
426
427                 if self._cam.getLastImage() is not None:
428                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
429                 else:
430                         self._preview.SetMinSize((640, 480))
431
432                 self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
433                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
434                 self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
435                 self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
436
437                 self.Fit()
438                 self.Centre()
439
440                 self._takeImageTimer.Start(200)
441
442         def OnCameraChange(self, e):
443                 self._cam.setActiveCamera(self._cameraSelect.GetSelection())
444
445         def OnTakeImage(self, e):
446                 self.GetParent()._addImage(self._cam.getLastImage())
447                 self.Destroy()
448
449         def OnTakeImageTimer(self, e):
450                 self._cam.takeNewImage(False)
451                 self.Refresh()
452
453         def OnCameraEraseBackground(self, e):
454                 dc = e.GetDC()
455                 if not dc:
456                         dc = wx.ClientDC(self)
457                         rect = self.GetUpdateRegion().GetBox()
458                         dc.SetClippingRect(rect)
459                 dc.SetBackground(wx.Brush(self._preview.GetBackgroundColour(), wx.SOLID))
460                 if self._cam.getLastImage() is not None:
461                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
462                         self.panel.Fit()
463                         dc.DrawBitmap(self._cam.getLastImage(), 0, 0)
464                 else:
465                         dc.Clear()