chiark / gitweb /
Add Ultimaker2 support.
[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                         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.
161                                 time.sleep(1)
162                                 self._ym.publishDesign(id)
163                 wx.CallAfter(self._indicatorWindow.Hide)
164
165                 webbrowser.open(self._ym.viewUrlForDesign(id))
166
167
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)
174
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'))
180                 ]
181
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))
187
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)
193
194                 self._busyState = 0
195                 self._busyTimer = wx.Timer(self)
196                 self.Bind(wx.EVT_TIMER, self._busyUpdate, self._busyTimer)
197                 self._busyTimer.Start(100)
198
199         def _busyUpdate(self, e):
200                 if self._busyState is None:
201                         return
202                 self._busyState += 1
203                 if self._busyState >= len(self._busyBitmaps):
204                         self._busyState = 0
205                 self._indicatorBitmap.SetBitmap(self._busyBitmaps[self._busyState])
206
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)
212
213         def showBusy(self, text):
214                 self._statusText.SetLabel(text)
215                 self._progress.Hide()
216                 self.Layout()
217                 self.Fit()
218                 self.Centre()
219                 self.Show()
220
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)
227                 self._ym = ym
228                 self.abort = False
229
230                 self._requestButton = wx.Button(self._panel, -1, 'Request authorization from YouMagine')
231                 self._authToken = wx.TextCtrl(self._panel, -1, 'Paste token here')
232
233                 self._panel._sizer = wx.GridBagSizer(5, 5)
234                 self._panel.SetSizer(self._panel._sizer)
235
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)
243
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)
247
248                 self.Fit()
249                 self.Centre()
250
251                 self._authToken.SetFocus()
252                 self._authToken.SelectAll()
253
254         def OnRequestAuthorization(self, e):
255                 webbrowser.open(self._ym.getAuthorizationUrl())
256
257         def OnEnterToken(self, e):
258                 self._ym.setAuthToken(self._authToken.GetValue())
259
260         def OnClose(self, e):
261                 self.abort = True
262
263 class newDesignWindow(wx.Frame):
264         def __init__(self, parent, manager, ym):
265                 super(newDesignWindow, self).__init__(parent, title='Share on YouMagine')
266                 p = wx.Panel(self)
267                 self.SetSizer(wx.BoxSizer())
268                 self.GetSizer().Add(p, 1, wx.EXPAND)
269                 self._manager = manager
270                 self._ym = ym
271
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, 'Share!')
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())
285
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)))
294
295                 self._publish.SetValue(True)
296                 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')
297
298                 s = wx.GridBagSizer(5, 5)
299                 p.SetSizer(s)
300
301                 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)
302                 s.Add(wx.StaticText(p, -1, 'Design name:'), (1, 0), flag=wx.LEFT|wx.TOP, border=5)
303                 s.Add(self._designName, (1, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
304                 s.Add(wx.StaticText(p, -1, 'Description:'), (2, 0), flag=wx.LEFT|wx.TOP, border=5)
305                 s.Add(self._designDescription, (2, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
306                 s.Add(wx.StaticText(p, -1, 'Category:'), (3, 0), flag=wx.LEFT|wx.TOP, border=5)
307                 s.Add(self._category, (3, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
308                 s.Add(wx.StaticText(p, -1, 'License:'), (4, 0), flag=wx.LEFT|wx.TOP, border=5)
309                 s.Add(self._designLicense, (4, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
310                 s.Add(wx.StaticLine(p, -1), (5,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
311                 s.Add(wx.StaticText(p, -1, 'Images:'), (6, 0), flag=wx.LEFT|wx.TOP, border=5)
312                 s.Add(self._imageScroll, (6, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
313                 s.Add(wx.StaticLine(p, -1), (7,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
314                 s.Add(wx.StaticText(p, -1, 'Related design files:'), (8, 0), flag=wx.LEFT|wx.TOP, border=5)
315                 s.Add(self._additionalFiles, (8, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
316                 s.Add(wx.StaticLine(p, -1), (9,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
317                 s.Add(self._shareButton, (10, 1), flag=wx.BOTTOM, border=15)
318                 s.Add(self._publish, (10, 2), flag=wx.BOTTOM|wx.ALIGN_CENTER_VERTICAL, border=15)
319
320                 s.AddGrowableRow(2)
321                 s.AddGrowableCol(2)
322
323                 self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
324                 self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
325                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
326
327                 self.Fit()
328                 self.Centre()
329
330                 self._designDescription.SetMinSize((1,1))
331                 self._designName.SetFocus()
332                 self._designName.SelectAll()
333
334         def OnShare(self, e):
335                 if self._designName.GetValue() == '':
336                         wx.MessageBox('The name cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
337                         self._designName.SetFocus()
338                         return
339                 if self._designDescription.GetValue() == '':
340                         wx.MessageBox('The description cannot be empty', 'New design error.', wx.OK | wx.ICON_ERROR)
341                         self._designDescription.SetFocus()
342                         return
343                 imageList = []
344                 for child in self._imageScroll.GetChildren():
345                         if hasattr(child, 'imageFilename'):
346                                 imageList.append(child.imageFilename)
347                         if hasattr(child, 'imageData'):
348                                 imageList.append(child.imageData)
349                 self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._additionalFiles.GetCheckedStrings(), self._publish.GetValue())
350                 self.Destroy()
351
352         def OnAddImage(self, e):
353                 dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
354                 dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
355                 if dlg.ShowModal() == wx.ID_OK:
356                         for filename in dlg.GetPaths():
357                                 self._addImage(filename)
358                 dlg.Destroy()
359
360         def OnTakeImage(self, e):
361                 webcamPhotoWindow(self).Show()
362
363         def _addImage(self, image):
364                 wxImage = None
365                 if type(image) in types.StringTypes:
366                         try:
367                                 wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
368                         except:
369                                 pass
370                 else:
371                         wxImage = wx.ImageFromBitmap(image)
372                 if wxImage is None:
373                         return
374
375                 width, height = wxImage.GetSize()
376                 if width > 70:
377                         height = height*70/width
378                         width = 70
379                 if height > 52:
380                         width = width*52/height
381                         height = 52
382                 wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
383                 wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
384                 ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
385                 if type(image) in types.StringTypes:
386                         ctrl.imageFilename = image
387                 else:
388                         ctrl.imageData = image
389
390                 delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
391                 self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
392
393                 self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
394                 self._imageScroll.Layout()
395                 self._imageScroll.Refresh()
396                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
397
398         def OnDeleteImage(self, e):
399                 ctrl = e.GetEventObject().GetParent()
400                 self._imageScroll.GetSizer().Detach(ctrl)
401                 ctrl.Destroy()
402
403                 self._imageScroll.Layout()
404                 self._imageScroll.Refresh()
405                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
406
407 class webcamPhotoWindow(wx.Frame):
408         def __init__(self, parent):
409                 super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
410                 p = wx.Panel(self)
411                 self.panel = p
412                 self.SetSizer(wx.BoxSizer())
413                 self.GetSizer().Add(p, 1, wx.EXPAND)
414
415                 self._cam = webcam.webcam()
416                 self._cam.takeNewImage(False)
417
418                 s = wx.GridBagSizer(3, 3)
419                 p.SetSizer(s)
420
421                 self._preview = wx.Panel(p)
422                 self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
423                 self._takeImageButton = wx.Button(p, -1, 'Snap image')
424                 self._takeImageTimer = wx.Timer(self)
425
426                 s.Add(self._takeImageButton, pos=(1, 0), flag=wx.ALL, border=5)
427                 s.Add(self._cameraSelect, pos=(1, 1), flag=wx.ALL, border=5)
428                 s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND|wx.ALL, border=5)
429
430                 if self._cam.getLastImage() is not None:
431                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
432                 else:
433                         self._preview.SetMinSize((640, 480))
434
435                 self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
436                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
437                 self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
438                 self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
439
440                 self.Fit()
441                 self.Centre()
442
443                 self._takeImageTimer.Start(200)
444
445         def OnCameraChange(self, e):
446                 self._cam.setActiveCamera(self._cameraSelect.GetSelection())
447
448         def OnTakeImage(self, e):
449                 self.GetParent()._addImage(self._cam.getLastImage())
450                 self.Destroy()
451
452         def OnTakeImageTimer(self, e):
453                 self._cam.takeNewImage(False)
454                 self.Refresh()
455
456         def OnCameraEraseBackground(self, e):
457                 dc = e.GetDC()
458                 if not dc:
459                         dc = wx.ClientDC(self)
460                         rect = self.GetUpdateRegion().GetBox()
461                         dc.SetClippingRect(rect)
462                 dc.SetBackground(wx.Brush(self._preview.GetBackgroundColour(), wx.SOLID))
463                 if self._cam.getLastImage() is not None:
464                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
465                         self.panel.Fit()
466                         dc.DrawBitmap(self._cam.getLastImage(), 0, 0)
467                 else:
468                         dc.Clear()