chiark / gitweb /
df87676dcbb16126d95d4bff8b57d8a86cba5614
[cura.git] / Cura / gui / sceneView.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 numpy
6 import time
7 import os
8 import traceback
9 import shutil
10 import threading
11 import math
12
13 import OpenGL
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
17
18 from Cura.gui import printWindow
19 from Cura.util import profile
20 from Cura.util import meshLoader
21 from Cura.util import objectScene
22 from Cura.util import resources
23 from Cura.util import sliceEngine
24 from Cura.util import machineCom
25 from Cura.util import removableStorage
26 from Cura.util import gcodeInterpreter
27 from Cura.gui.util import previewTools
28 from Cura.gui.util import opengl
29 from Cura.gui.util import openglGui
30
31 class SceneView(openglGui.glGuiPanel):
32         def __init__(self, parent):
33                 super(SceneView, self).__init__(parent)
34
35                 self._yaw = 30
36                 self._pitch = 60
37                 self._zoom = 300
38                 self._scene = objectScene.Scene()
39                 self._gcode = None
40                 self._gcodeVBOs = []
41                 self._objectShader = None
42                 self._focusObj = None
43                 self._selectedObj = None
44                 self._objColors = [None,None,None,None]
45                 self._mouseX = -1
46                 self._mouseY = -1
47                 self._mouseState = None
48                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
49                 self._animView = None
50                 self._animZoom = None
51                 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
52                 self._platformMesh._drawOffset = numpy.array([0,0,2.5], numpy.float32)
53                 self._isSimpleMode = True
54
55                 self._viewport = None
56                 self._modelMatrix = None
57                 self._projMatrix = None
58                 self.tempMatrix = None
59
60                 self.openFileButton      = openglGui.glButton(self, 4, 'Load', (0,0), self.showLoadModel)
61                 self.printButton         = openglGui.glButton(self, 6, 'Print', (1,0), self.showPrintWindow)
62                 self.printButton.setDisabled(True)
63
64                 group = []
65                 self.rotateToolButton = openglGui.glRadioButton(self, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
66                 self.scaleToolButton  = openglGui.glRadioButton(self, 9, 'Scale', (1,-1), group, self.OnToolSelect)
67                 self.mirrorToolButton  = openglGui.glRadioButton(self, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
68
69                 self.resetRotationButton = openglGui.glButton(self, 12, 'Reset', (0,-2), self.OnRotateReset)
70                 self.layFlatButton       = openglGui.glButton(self, 16, 'Lay flat', (0,-3), self.OnLayFlat)
71
72                 self.resetScaleButton    = openglGui.glButton(self, 13, 'Reset', (1,-2), self.OnScaleReset)
73                 self.scaleMaxButton      = openglGui.glButton(self, 17, 'To max', (1,-3), self.OnScaleMax)
74
75                 self.mirrorXButton       = openglGui.glButton(self, 14, 'Mirror X', (2,-2), lambda button: self.OnMirror(0))
76                 self.mirrorYButton       = openglGui.glButton(self, 18, 'Mirror Y', (2,-3), lambda button: self.OnMirror(1))
77                 self.mirrorZButton       = openglGui.glButton(self, 22, 'Mirror Z', (2,-4), lambda button: self.OnMirror(2))
78
79                 self.rotateToolButton.setExpandArrow(True)
80                 self.scaleToolButton.setExpandArrow(True)
81                 self.mirrorToolButton.setExpandArrow(True)
82
83                 self.scaleForm = openglGui.glFrame(self, (2, -2))
84                 openglGui.glGuiLayoutGrid(self.scaleForm)
85                 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
86                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
87                 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
88                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
89                 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
90                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
91                 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
92                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
93                 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
94                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
95                 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
96                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
97                 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
98                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
99
100                 self.viewSelection = openglGui.glComboButton(self, 'View mode', [7,11,15,23], ['Normal', 'Transparent', 'X-Ray', 'Layers'], (-1,0), self.OnViewChange)
101                 self.layerSelect = openglGui.glSlider(self, 100, 0, 100, (-1,-2), lambda : self.QueueRefresh())
102
103                 self.notification = openglGui.glNotification(self, (0, 0))
104
105                 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
106                 self._sceneUpdateTimer = wx.Timer(self)
107                 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
108                 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
109
110                 self.OnViewChange()
111                 self.OnToolSelect(0)
112                 self.updateToolButtons()
113                 self.updateProfileToControls()
114
115         def showLoadModel(self, button = 1):
116                 if button == 1:
117                         dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
118                         dlg.SetWildcard(meshLoader.loadWildcardFilter() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
119                         if dlg.ShowModal() != wx.ID_OK:
120                                 dlg.Destroy()
121                                 return
122                         filename = dlg.GetPath()
123                         dlg.Destroy()
124                         if not(os.path.exists(filename)):
125                                 return False
126                         profile.putPreference('lastFile', filename)
127                         self.GetParent().GetParent().GetParent().addToModelMRU(filename)
128                         ext = filename[filename.rfind('.')+1:].upper()
129                         if ext == 'G' or ext == 'GCODE':
130                                 if self._gcode is not None:
131                                         self._gcode = None
132                                         for layerVBOlist in self._gcodeVBOs:
133                                                 for vbo in layerVBOlist:
134                                                         self.glReleaseList.append(vbo)
135                                         self._gcodeVBOs = []
136                                 self._gcode = gcodeInterpreter.gcode()
137                                 self._gcode.progressCallback = self._gcodeLoadCallback
138                                 self._thread = threading.Thread(target=self._loadGCode, args=(filename,))
139                                 self._thread.daemon = True
140                                 self._thread.start()
141                                 self.printButton.setBottomText('')
142                                 self.viewSelection.setValue(3)
143                                 self.printButton.setDisabled(False)
144                                 self.OnViewChange()
145                         else:
146                                 if self.viewSelection.getValue() == 3:
147                                         self.viewSelection.setValue(0)
148                                         self.OnViewChange()
149                                 self.loadScene([filename])
150
151         def showSaveModel(self):
152                 if len(self._scene.objects()) < 1:
153                         return
154                 dlg=wx.FileDialog(self, 'Save 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
155                 dlg.SetWildcard(meshLoader.saveWildcardFilter())
156                 if dlg.ShowModal() != wx.ID_OK:
157                         dlg.Destroy()
158                         return
159                 filename = dlg.GetPath()
160                 dlg.Destroy()
161                 meshLoader.saveMeshes(filename, self._scene.objects())
162
163         def showPrintWindow(self, button):
164                 if button == 1:
165                         if machineCom.machineIsConnected():
166                                 printWindow.printFile(self._gcode.filename)
167                         elif len(removableStorage.getPossibleSDcardDrives()) > 0:
168                                 drives = removableStorage.getPossibleSDcardDrives()
169                                 if len(drives) > 1:
170                                         dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
171                                         if dlg.ShowModal() != wx.ID_OK:
172                                                 dlg.Destroy()
173                                                 return
174                                         drive = drives[dlg.GetSelection()]
175                                         dlg.Destroy()
176                                 else:
177                                         drive = drives[0]
178                                 filename = os.path.basename(profile.getPreference('lastFile'))
179                                 filename = filename[0:filename.rfind('.')] + '.gcode'
180                                 threading.Thread(target=self._copyFile,args=(self._gcode.filename, drive[1] + filename, drive[1])).start()
181                         else:
182                                 self.showSaveGCode()
183                 if button == 3:
184                         menu = wx.Menu()
185                         self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._gcode.filename), menu.Append(-1, 'Print with USB'))
186                         self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, 'Save GCode...'))
187                         self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
188                         self.PopupMenu(menu)
189                         menu.Destroy()
190
191         def showSaveGCode(self):
192                 defPath = profile.getPreference('lastFile')
193                 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
194                 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
195                 dlg.SetFilename(os.path.basename(defPath))
196                 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
197                 if dlg.ShowModal() != wx.ID_OK:
198                         dlg.Destroy()
199                         return
200                 filename = dlg.GetPath()
201                 dlg.Destroy()
202
203                 threading.Thread(target=self._copyFile,args=(self._gcode.filename, filename)).start()
204
205         def _copyFile(self, fileA, fileB, allowEject = False):
206                 try:
207                         size = float(os.stat(fileA).st_size)
208                         with open(fileA, 'rb') as fsrc:
209                                 with open(fileB, 'wb') as fdst:
210                                         while 1:
211                                                 buf = fsrc.read(16*1024)
212                                                 if not buf:
213                                                         break
214                                                 fdst.write(buf)
215                                                 self.printButton.setProgressBar(float(fsrc.tell()) / size)
216                                                 self._queueRefresh()
217                 except:
218                         self.notification.message("Failed to save")
219                 else:
220                         if allowEject:
221                                 self.notification.message("Saved as %s" % (fileB), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(allowEject) else self.notification.message('Safe remove failed...'))
222                         else:
223                                 self.notification.message("Saved as %s" % (fileB))
224                 self.printButton.setProgressBar(None)
225
226
227         def _showSliceLog(self):
228                 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
229                 dlg.ShowModal()
230                 dlg.Destroy()
231
232         def OnToolSelect(self, button):
233                 if self.rotateToolButton.getSelected():
234                         self.tool = previewTools.toolRotate(self)
235                 elif self.scaleToolButton.getSelected():
236                         self.tool = previewTools.toolScale(self)
237                 elif self.mirrorToolButton.getSelected():
238                         self.tool = previewTools.toolNone(self)
239                 else:
240                         self.tool = previewTools.toolNone(self)
241                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
242                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
243                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
244                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
245                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
246                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
247                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
248                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
249
250         def updateToolButtons(self):
251                 if self._selectedObj is None:
252                         hidden = True
253                 else:
254                         hidden = False
255                 self.rotateToolButton.setHidden(hidden)
256                 self.scaleToolButton.setHidden(hidden)
257                 self.mirrorToolButton.setHidden(hidden)
258                 if hidden:
259                         self.rotateToolButton.setSelected(False)
260                         self.scaleToolButton.setSelected(False)
261                         self.mirrorToolButton.setSelected(False)
262                         self.OnToolSelect(0)
263
264         def OnViewChange(self):
265                 if self.viewSelection.getValue() == 3:
266                         self.viewMode = 'gcode'
267                         if self._gcode is not None:
268                                 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
269                                 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
270                         self._selectObject(None)
271                 elif self.viewSelection.getValue() == 1:
272                         self.viewMode = 'transparent'
273                 elif self.viewSelection.getValue() == 2:
274                         self.viewMode = 'xray'
275                 else:
276                         self.viewMode = 'normal'
277                 self.layerSelect.setHidden(self.viewMode != 'gcode')
278                 self.QueueRefresh()
279
280         def OnRotateReset(self, button):
281                 if self._selectedObj is None:
282                         return
283                 self._selectedObj.resetRotation()
284                 self._scene.pushFree()
285                 self._selectObject(self._selectedObj)
286                 self.sceneUpdated()
287
288         def OnLayFlat(self, button):
289                 if self._selectedObj is None:
290                         return
291                 self._selectedObj.layFlat()
292                 self._scene.pushFree()
293                 self._selectObject(self._selectedObj)
294                 self.sceneUpdated()
295
296         def OnScaleReset(self, button):
297                 if self._selectedObj is None:
298                         return
299                 self._selectedObj.resetScale()
300                 self._selectObject(self._selectedObj)
301                 self.updateProfileToControls()
302                 self.sceneUpdated()
303
304         def OnScaleMax(self, button):
305                 if self._selectedObj is None:
306                         return
307                 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
308                 self._scene.pushFree()
309                 self._selectObject(self._selectedObj)
310                 self.updateProfileToControls()
311                 self.sceneUpdated()
312
313         def OnMirror(self, axis):
314                 if self._selectedObj is None:
315                         return
316                 self._selectedObj.mirror(axis)
317                 self.sceneUpdated()
318
319         def OnScaleEntry(self, value, axis):
320                 if self._selectedObj is None:
321                         return
322                 try:
323                         value = float(value)
324                 except:
325                         return
326                 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
327                 self.updateProfileToControls()
328                 self._scene.pushFree()
329                 self._selectObject(self._selectedObj)
330                 self.sceneUpdated()
331
332         def OnScaleEntryMM(self, value, axis):
333                 if self._selectedObj is None:
334                         return
335                 try:
336                         value = float(value)
337                 except:
338                         return
339                 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
340                 self.updateProfileToControls()
341                 self._scene.pushFree()
342                 self._selectObject(self._selectedObj)
343                 self.sceneUpdated()
344
345         def OnDeleteAll(self, e):
346                 while len(self._scene.objects()) > 0:
347                         self._deleteObject(self._scene.objects()[0])
348                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
349
350         def OnMultiply(self, e):
351                 if self._focusObj is None:
352                         return
353                 obj = self._focusObj
354                 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
355                 if dlg.ShowModal() != wx.ID_OK:
356                         dlg.Destroy()
357                         return
358                 cnt = dlg.GetValue() - 1
359                 dlg.Destroy()
360                 n = 0
361                 while True:
362                         n += 1
363                         newObj = obj.copy()
364                         self._scene.add(newObj)
365                         self._scene.centerAll()
366                         if not self._scene.checkPlatform(newObj):
367                                 break
368                         if n > cnt:
369                                 break
370                 if n <= cnt:
371                         self.notification.message("Could not create more then %d items" % (n))
372                 self._scene.remove(newObj)
373                 self._scene.centerAll()
374                 self.sceneUpdated()
375
376         def OnSplitObject(self, e):
377                 if self._focusObj is None:
378                         return
379                 self._scene.remove(self._focusObj)
380                 for obj in self._focusObj.split(self._splitCallback):
381                         self._scene.add(obj)
382                 self._scene.centerAll()
383                 self._selectObject(None)
384                 self.sceneUpdated()
385
386         def _splitCallback(self, progress):
387                 print progress
388
389         def OnMergeObjects(self, e):
390                 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
391                         return
392                 self._scene.merge(self._selectedObj, self._focusObj)
393                 self.sceneUpdated()
394
395         def sceneUpdated(self):
396                 self._sceneUpdateTimer.Start(1, True)
397                 self._slicer.abortSlicer()
398                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
399                 self.QueueRefresh()
400
401         def _onRunSlicer(self, e):
402                 if self._isSimpleMode:
403                         self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
404                 self._slicer.runSlicer(self._scene)
405                 if self._isSimpleMode:
406                         profile.resetTempOverride()
407
408         def _updateSliceProgress(self, progressValue, ready):
409                 self.printButton.setDisabled(not ready)
410                 if progressValue >= 0.0:
411                         self.printButton.setProgressBar(progressValue)
412                 else:
413                         self.printButton.setProgressBar(None)
414                 if self._gcode is not None:
415                         self._gcode = None
416                         for layerVBOlist in self._gcodeVBOs:
417                                 for vbo in layerVBOlist:
418                                         self.glReleaseList.append(vbo)
419                         self._gcodeVBOs = []
420                 if ready:
421                         self.printButton.setProgressBar(None)
422                         cost = self._slicer.getFilamentCost()
423                         if cost is not None:
424                                 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
425                         else:
426                                 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
427                         self._gcode = gcodeInterpreter.gcode()
428                         self._gcode.progressCallback = self._gcodeLoadCallback
429                         self._thread = threading.Thread(target=self._loadGCode)
430                         self._thread.daemon = True
431                         self._thread.start()
432                 else:
433                         self.printButton.setBottomText('')
434                 self.QueueRefresh()
435
436         def _loadGCode(self, filename = None):
437                 if filename is None:
438                         filename = self._slicer.getGCodeFilename()
439                 self._gcode.load(filename)
440
441         def _gcodeLoadCallback(self, progress):
442                 if self._gcode is None:
443                         return True
444                 if len(self._gcode.layerList) % 5 == 0:
445                         time.sleep(0.1)
446                 if self._gcode is None:
447                         return True
448                 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
449                         self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
450                         self.layerSelect.setValue(self.layerSelect.getMaxValue())
451                 else:
452                         self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
453                 if self.viewMode == 'gcode':
454                         self._queueRefresh()
455                 return False
456
457         def loadScene(self, fileList):
458                 for filename in fileList:
459                         try:
460                                 objList = meshLoader.loadMeshes(filename)
461                         except:
462                                 traceback.print_exc()
463                         else:
464                                 for obj in objList:
465                                         obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
466                                         self._scene.add(obj)
467                                         self._scene.centerAll()
468                                         self._selectObject(obj)
469                 self.sceneUpdated()
470
471         def _deleteObject(self, obj):
472                 if obj == self._selectedObj:
473                         self._selectObject(None)
474                 if obj == self._focusObj:
475                         self._focusObj = None
476                 self._scene.remove(obj)
477                 for m in obj._meshList:
478                         if m.vbo is not None and m.vbo.decRef():
479                                 self.glReleaseList.append(m.vbo)
480                 import gc
481                 gc.collect()
482                 self.sceneUpdated()
483
484         def _selectObject(self, obj, zoom = True):
485                 if obj != self._selectedObj:
486                         self._selectedObj = obj
487                         self.updateProfileToControls()
488                         self.updateToolButtons()
489                 if zoom and obj is not None:
490                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
491                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
492                         newZoom = obj.getBoundaryCircle() * 6
493                         if newZoom > numpy.max(self._machineSize) * 3:
494                                 newZoom = numpy.max(self._machineSize) * 3
495                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
496
497         def updateProfileToControls(self):
498                 oldSimpleMode = self._isSimpleMode
499                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
500                 if self._isSimpleMode and not oldSimpleMode:
501                         self._scene.arrangeAll()
502                         self.sceneUpdated()
503                 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
504                 self._objColors[0] = profile.getPreferenceColour('model_colour')
505                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
506                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
507                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
508                 self._scene.setMachineSize(self._machineSize)
509                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
510                 self._scene.setHeadSize(profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_min_y'), profile.getPreferenceFloat('extruder_head_size_max_y'), profile.getPreferenceFloat('extruder_head_size_height'))
511
512                 if self._selectedObj is not None:
513                         scale = self._selectedObj.getScale()
514                         size = self._selectedObj.getSize()
515                         self.scaleXctrl.setValue(round(scale[0], 2))
516                         self.scaleYctrl.setValue(round(scale[1], 2))
517                         self.scaleZctrl.setValue(round(scale[2], 2))
518                         self.scaleXmmctrl.setValue(round(size[0], 2))
519                         self.scaleYmmctrl.setValue(round(size[1], 2))
520                         self.scaleZmmctrl.setValue(round(size[2], 2))
521
522         def OnKeyChar(self, keyCode):
523                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
524                         if self._selectedObj is not None:
525                                 self._deleteObject(self._selectedObj)
526                                 self.QueueRefresh()
527                 if keyCode == wx.WXK_UP:
528                         self.layerSelect.setValue(self.layerSelect.getValue() + 1)
529                         self.QueueRefresh()
530                 elif keyCode == wx.WXK_DOWN:
531                         self.layerSelect.setValue(self.layerSelect.getValue() - 1)
532                         self.QueueRefresh()
533                 elif keyCode == wx.WXK_PAGEUP:
534                         self.layerSelect.setValue(self.layerSelect.getValue() + 10)
535                         self.QueueRefresh()
536                 elif keyCode == wx.WXK_PAGEDOWN:
537                         self.layerSelect.setValue(self.layerSelect.getValue() - 10)
538                         self.QueueRefresh()
539
540                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
541                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
542                 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
543                         from collections import defaultdict
544                         from gc import get_objects
545                         self._beforeLeakTest = defaultdict(int)
546                         for i in get_objects():
547                                 self._beforeLeakTest[type(i)] += 1
548                 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
549                         from collections import defaultdict
550                         from gc import get_objects
551                         self._afterLeakTest = defaultdict(int)
552                         for i in get_objects():
553                                 self._afterLeakTest[type(i)] += 1
554                         for k in self._afterLeakTest:
555                                 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
556                                         print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
557
558         def ShaderUpdate(self, v, f):
559                 s = opengl.GLShader(v, f)
560                 if s.isValid():
561                         self._objectLoadShader.release()
562                         self._objectLoadShader = s
563                         for obj in self._scene.objects():
564                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
565                         self.QueueRefresh()
566
567         def OnMouseDown(self,e):
568                 self._mouseX = e.GetX()
569                 self._mouseY = e.GetY()
570                 self._mouseClick3DPos = self._mouse3Dpos
571                 self._mouseClickFocus = self._focusObj
572                 if e.ButtonDClick():
573                         self._mouseState = 'doubleClick'
574                 else:
575                         self._mouseState = 'dragOrClick'
576                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
577                 p0 -= self.getObjectCenterPos() - self._viewTarget
578                 p1 -= self.getObjectCenterPos() - self._viewTarget
579                 if self.tool.OnDragStart(p0, p1):
580                         self._mouseState = 'tool'
581                 if self._mouseState == 'dragOrClick':
582                         if e.GetButton() == 1:
583                                 if self._focusObj is not None:
584                                         self._selectObject(self._focusObj, False)
585                                         self.QueueRefresh()
586
587         def OnMouseUp(self, e):
588                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
589                         return
590                 if self._mouseState == 'dragOrClick':
591                         if e.GetButton() == 1:
592                                 self._selectObject(self._focusObj)
593                         if e.GetButton() == 3:
594                                         menu = wx.Menu()
595                                         if self._focusObj is not None:
596                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
597                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
598                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
599                                         if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
600                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
601                                         if len(self._scene.objects()) > 0:
602                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
603                                         if menu.MenuItemCount > 0:
604                                                 self.PopupMenu(menu)
605                                         menu.Destroy()
606                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
607                         self._scene.pushFree()
608                         self.sceneUpdated()
609                 elif self._mouseState == 'tool':
610                         if self.tempMatrix is not None and self._selectedObj is not None:
611                                 self._selectedObj.applyMatrix(self.tempMatrix)
612                                 self._scene.pushFree()
613                                 self._selectObject(self._selectedObj)
614                         self.tempMatrix = None
615                         self.tool.OnDragEnd()
616                         self.sceneUpdated()
617                 self._mouseState = None
618
619         def OnMouseMotion(self,e):
620                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
621                 p0 -= self.getObjectCenterPos() - self._viewTarget
622                 p1 -= self.getObjectCenterPos() - self._viewTarget
623
624                 if e.Dragging() and self._mouseState is not None:
625                         if self._mouseState == 'tool':
626                                 self.tool.OnDrag(p0, p1)
627                         elif not e.LeftIsDown() and e.RightIsDown():
628                                 self._mouseState = 'drag'
629                                 if wx.GetKeyState(wx.WXK_SHIFT):
630                                         a = math.cos(math.radians(self._yaw)) / 3.0
631                                         b = math.sin(math.radians(self._yaw)) / 3.0
632                                         self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
633                                         self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
634                                         self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
635                                         self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
636                                 else:
637                                         self._yaw += e.GetX() - self._mouseX
638                                         self._pitch -= e.GetY() - self._mouseY
639                                 if self._pitch > 170:
640                                         self._pitch = 170
641                                 if self._pitch < 10:
642                                         self._pitch = 10
643                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
644                                 self._mouseState = 'drag'
645                                 self._zoom += e.GetY() - self._mouseY
646                                 if self._zoom < 1:
647                                         self._zoom = 1
648                                 if self._zoom > numpy.max(self._machineSize) * 3:
649                                         self._zoom = numpy.max(self._machineSize) * 3
650                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
651                                 self._mouseState = 'dragObject'
652                                 z = max(0, self._mouseClick3DPos[2])
653                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
654                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
655                                 p0[2] -= z
656                                 p1[2] -= z
657                                 p2[2] -= z
658                                 p3[2] -= z
659                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
660                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
661                                 diff = cursorZ1 - cursorZ0
662                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
663                 if not e.Dragging() or self._mouseState != 'tool':
664                         self.tool.OnMouseMove(p0, p1)
665
666                 self._mouseX = e.GetX()
667                 self._mouseY = e.GetY()
668
669         def OnMouseWheel(self, e):
670                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
671                 delta = max(min(delta,4),-4)
672                 self._zoom *= 1.0 - delta / 10.0
673                 if self._zoom < 1.0:
674                         self._zoom = 1.0
675                 if self._zoom > numpy.max(self._machineSize) * 3:
676                         self._zoom = numpy.max(self._machineSize) * 3
677                 self.Refresh()
678
679         def getMouseRay(self, x, y):
680                 if self._viewport is None:
681                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
682                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
683                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
684                 p0 -= self._viewTarget
685                 p1 -= self._viewTarget
686                 return p0, p1
687
688         def _init3DView(self):
689                 # set viewing projection
690                 size = self.GetSize()
691                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
692                 glLoadIdentity()
693
694                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
695
696                 glDisable(GL_RESCALE_NORMAL)
697                 glDisable(GL_LIGHTING)
698                 glDisable(GL_LIGHT0)
699                 glEnable(GL_DEPTH_TEST)
700                 glDisable(GL_CULL_FACE)
701                 glDisable(GL_BLEND)
702                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
703
704                 glClearColor(0.8, 0.8, 0.8, 1.0)
705                 glClearStencil(0)
706                 glClearDepth(1.0)
707
708                 glMatrixMode(GL_PROJECTION)
709                 glLoadIdentity()
710                 aspect = float(size.GetWidth()) / float(size.GetHeight())
711                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
712
713                 glMatrixMode(GL_MODELVIEW)
714                 glLoadIdentity()
715                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
716
717         def OnPaint(self,e):
718                 if machineCom.machineIsConnected():
719                         self.printButton._imageID = 6
720                         self.printButton._tooltip = 'Print'
721                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
722                         self.printButton._imageID = 2
723                         self.printButton._tooltip = 'Toolpath to SD'
724                 else:
725                         self.printButton._imageID = 3
726                         self.printButton._tooltip = 'Save toolpath'
727
728                 if self._animView is not None:
729                         self._viewTarget = self._animView.getPosition()
730                         if self._animView.isDone():
731                                 self._animView = None
732                 if self._animZoom is not None:
733                         self._zoom = self._animZoom.getPosition()
734                         if self._animZoom.isDone():
735                                 self._animZoom = None
736                 if self.viewMode == 'gcode' and self._gcode is not None:
737                         try:
738                                 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
739                         except:
740                                 pass
741                 if self._objectShader is None:
742                         self._objectShader = opengl.GLShader("""
743 varying float light_amount;
744
745 void main(void)
746 {
747     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
748     gl_FrontColor = gl_Color;
749
750         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
751         light_amount += 0.2;
752 }
753                         ""","""
754 varying float light_amount;
755
756 void main(void)
757 {
758         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
759 }
760                         """)
761                         self._objectLoadShader = opengl.GLShader("""
762 uniform float intensity;
763 uniform float scale;
764 varying float light_amount;
765
766 void main(void)
767 {
768         vec4 tmp = gl_Vertex;
769     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
770     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
771     gl_Position = gl_ModelViewProjectionMatrix * tmp;
772     gl_FrontColor = gl_Color;
773
774         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
775         light_amount += 0.2;
776 }
777                         ""","""
778 uniform float intensity;
779 varying float light_amount;
780
781 void main(void)
782 {
783         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
784 }
785                         """)
786                 self._init3DView()
787                 glTranslate(0,0,-self._zoom)
788                 glRotate(-self._pitch, 1,0,0)
789                 glRotate(self._yaw, 0,0,1)
790                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
791
792                 self._viewport = glGetIntegerv(GL_VIEWPORT)
793                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
794                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
795
796                 glClearColor(1,1,1,1)
797                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
798
799                 if self.viewMode != 'gcode':
800                         for n in xrange(0, len(self._scene.objects())):
801                                 obj = self._scene.objects()[n]
802                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
803                                 self._renderObject(obj)
804
805                 if self._mouseX > -1:
806                         glFlush()
807                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
808                         if n < len(self._scene.objects()):
809                                 self._focusObj = self._scene.objects()[n]
810                         else:
811                                 self._focusObj = None
812                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
813                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
814                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
815                         self._mouse3Dpos -= self._viewTarget
816
817                 self._init3DView()
818                 glTranslate(0,0,-self._zoom)
819                 glRotate(-self._pitch, 1,0,0)
820                 glRotate(self._yaw, 0,0,1)
821                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
822
823                 if self.viewMode == 'gcode':
824                         if self._gcode is not None:
825                                 glPushMatrix()
826                                 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
827                                 t = time.time()
828                                 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
829                                 for n in xrange(0, drawUpTill):
830                                         c = 1.0 - float(drawUpTill - n) / 15
831                                         c = max(0.3, c)
832                                         if len(self._gcodeVBOs) < n + 1:
833                                                 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
834                                                 if time.time() - t > 0.5:
835                                                         self.QueueRefresh()
836                                                         break
837                                         #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
838                                         if n == drawUpTill - 1:
839                                                 if len(self._gcodeVBOs[n]) < 6:
840                                                         self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
841                                                 glColor3f(c, 0, 0)
842                                                 self._gcodeVBOs[n][5].render(GL_QUADS)
843                                                 glColor3f(0, c, 0)
844                                                 self._gcodeVBOs[n][6].render(GL_QUADS)
845                                                 glColor3f(c/2, c/2, 0.0)
846                                                 self._gcodeVBOs[n][7].render(GL_QUADS)
847                                                 glColor3f(0, c, c)
848                                                 self._gcodeVBOs[n][8].render(GL_QUADS)
849                                                 self._gcodeVBOs[n][9].render(GL_QUADS)
850                                                 glColor3f(0, 0, c)
851                                                 self._gcodeVBOs[n][10].render(GL_LINES)
852                                         else:
853                                                 glColor3f(c, 0, 0)
854                                                 self._gcodeVBOs[n][0].render(GL_LINES)
855                                                 glColor3f(0, c, 0)
856                                                 self._gcodeVBOs[n][1].render(GL_LINES)
857                                                 glColor3f(c/2, c/2, 0.0)
858                                                 self._gcodeVBOs[n][2].render(GL_LINES)
859                                                 glColor3f(0, c, c)
860                                                 self._gcodeVBOs[n][3].render(GL_LINES)
861                                                 self._gcodeVBOs[n][4].render(GL_LINES)
862                                 glPopMatrix()
863                 else:
864                         glStencilFunc(GL_ALWAYS, 1, 1)
865                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
866
867                         self._objectShader.bind()
868                         for obj in self._scene.objects():
869                                 if obj._loadAnim is not None:
870                                         if obj._loadAnim.isDone():
871                                                 obj._loadAnim = None
872                                         else:
873                                                 continue
874                                 brightness = 1.0
875                                 if self._focusObj == obj:
876                                         brightness = 1.2
877                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
878                                         brightness = 0.8
879
880                                 if self._selectedObj == obj or self._selectedObj is None:
881                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
882                                         if self.viewMode == 'transparent':
883                                                 glColor4f(0, 0, 0, 0)
884                                                 self._renderObject(obj)
885                                                 glEnable(GL_BLEND)
886                                                 glBlendFunc(GL_ONE, GL_ONE)
887                                                 glDisable(GL_DEPTH_TEST)
888                                                 brightness *= 0.5
889                                         if self.viewMode == 'xray':
890                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
891                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
892                                         glEnable(GL_STENCIL_TEST)
893
894                                 if not self._scene.checkPlatform(obj):
895                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
896                                         self._renderObject(obj)
897                                 else:
898                                         self._renderObject(obj, brightness)
899                                 glDisable(GL_STENCIL_TEST)
900                                 glDisable(GL_BLEND)
901                                 glEnable(GL_DEPTH_TEST)
902                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
903
904                         if self.viewMode == 'xray':
905                                 glPushMatrix()
906                                 glLoadIdentity()
907                                 glEnable(GL_STENCIL_TEST)
908                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
909                                 glDisable(GL_DEPTH_TEST)
910                                 for i in xrange(2, 15, 2):
911                                         glStencilFunc(GL_EQUAL, i, 0xFF)
912                                         glColor(float(i)/10, float(i)/10, float(i)/5)
913                                         glBegin(GL_QUADS)
914                                         glVertex3f(-1000,-1000,-10)
915                                         glVertex3f( 1000,-1000,-10)
916                                         glVertex3f( 1000, 1000,-10)
917                                         glVertex3f(-1000, 1000,-10)
918                                         glEnd()
919                                 for i in xrange(1, 15, 2):
920                                         glStencilFunc(GL_EQUAL, i, 0xFF)
921                                         glColor(float(i)/10, 0, 0)
922                                         glBegin(GL_QUADS)
923                                         glVertex3f(-1000,-1000,-10)
924                                         glVertex3f( 1000,-1000,-10)
925                                         glVertex3f( 1000, 1000,-10)
926                                         glVertex3f(-1000, 1000,-10)
927                                         glEnd()
928                                 glPopMatrix()
929                                 glDisable(GL_STENCIL_TEST)
930                                 glEnable(GL_DEPTH_TEST)
931
932                         self._objectShader.unbind()
933
934                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
935                         glEnable(GL_BLEND)
936                         self._objectLoadShader.bind()
937                         glColor4f(0.2, 0.6, 1.0, 1.0)
938                         for obj in self._scene.objects():
939                                 if obj._loadAnim is None:
940                                         continue
941                                 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
942                                 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
943                                 self._renderObject(obj)
944                         self._objectLoadShader.unbind()
945                         glDisable(GL_BLEND)
946
947                 self._drawMachine()
948
949                 if self.viewMode == 'gcode':
950                         pass
951                 else:
952                         #Draw the object box-shadow, so you can see where it will collide with other objects.
953                         if self._selectedObj is not None and len(self._scene.objects()) > 1:
954                                 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
955                                 glPushMatrix()
956                                 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
957                                 glEnable(GL_BLEND)
958                                 glEnable(GL_CULL_FACE)
959                                 glColor4f(0,0,0,0.12)
960                                 glBegin(GL_QUADS)
961                                 glVertex3f(-size[0],  size[1], 0.1)
962                                 glVertex3f(-size[0], -size[1], 0.1)
963                                 glVertex3f( size[0], -size[1], 0.1)
964                                 glVertex3f( size[0],  size[1], 0.1)
965                                 glEnd()
966                                 glDisable(GL_CULL_FACE)
967                                 glPopMatrix()
968
969                         #Draw the outline of the selected object, on top of everything else except the GUI.
970                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
971                                 glDisable(GL_DEPTH_TEST)
972                                 glEnable(GL_CULL_FACE)
973                                 glEnable(GL_STENCIL_TEST)
974                                 glDisable(GL_BLEND)
975                                 glStencilFunc(GL_EQUAL, 0, 255)
976
977                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
978                                 glLineWidth(2)
979                                 glColor4f(1,1,1,0.5)
980                                 self._renderObject(self._selectedObj)
981                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
982
983                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
984                                 glDisable(GL_STENCIL_TEST)
985                                 glDisable(GL_CULL_FACE)
986                                 glEnable(GL_DEPTH_TEST)
987
988                         if self._selectedObj is not None:
989                                 glPushMatrix()
990                                 pos = self.getObjectCenterPos()
991                                 glTranslate(pos[0], pos[1], pos[2])
992                                 self.tool.OnDraw()
993                                 glPopMatrix()
994
995         def _renderObject(self, obj, brightness = False, addSink = True):
996                 glPushMatrix()
997                 if addSink:
998                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
999                 else:
1000                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1001
1002                 if self.tempMatrix is not None and obj == self._selectedObj:
1003                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1004                         glMultMatrixf(tempMatrix)
1005
1006                 offset = obj.getDrawOffset()
1007                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1008
1009                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1010                 glMultMatrixf(tempMatrix)
1011
1012                 n = 0
1013                 for m in obj._meshList:
1014                         if m.vbo is None:
1015                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1016                         if brightness:
1017                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1018                                 n += 1
1019                         m.vbo.render()
1020                 glPopMatrix()
1021
1022         def _drawMachine(self):
1023                 glEnable(GL_CULL_FACE)
1024                 glEnable(GL_BLEND)
1025
1026                 if profile.getPreference('machine_type') == 'ultimaker':
1027                         glColor4f(1,1,1,0.5)
1028                         self._objectShader.bind()
1029                         self._renderObject(self._platformMesh, False, False)
1030                         self._objectShader.unbind()
1031
1032                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1033                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1034                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1035                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1036                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1037                 v4 = [ size[0] / 2, size[1] / 2, 0]
1038                 v5 = [ size[0] / 2,-size[1] / 2, 0]
1039                 v6 = [-size[0] / 2, size[1] / 2, 0]
1040                 v7 = [-size[0] / 2,-size[1] / 2, 0]
1041
1042                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1043                 glEnableClientState(GL_VERTEX_ARRAY)
1044                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1045
1046                 glColor4ub(5, 171, 231, 64)
1047                 glDrawArrays(GL_QUADS, 0, 4)
1048                 glColor4ub(5, 171, 231, 96)
1049                 glDrawArrays(GL_QUADS, 4, 8)
1050                 glColor4ub(5, 171, 231, 128)
1051                 glDrawArrays(GL_QUADS, 12, 8)
1052
1053                 sx = self._machineSize[0]
1054                 sy = self._machineSize[1]
1055                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1056                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1057                                 x1 = x * 10
1058                                 x2 = x1 + 10
1059                                 y1 = y * 10
1060                                 y2 = y1 + 10
1061                                 x1 = max(min(x1, sx/2), -sx/2)
1062                                 y1 = max(min(y1, sy/2), -sy/2)
1063                                 x2 = max(min(x2, sx/2), -sx/2)
1064                                 y2 = max(min(y2, sy/2), -sy/2)
1065                                 if (x & 1) == (y & 1):
1066                                         glColor4ub(5, 171, 231, 127)
1067                                 else:
1068                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1069                                 glBegin(GL_QUADS)
1070                                 glVertex3f(x1, y1, -0.02)
1071                                 glVertex3f(x2, y1, -0.02)
1072                                 glVertex3f(x2, y2, -0.02)
1073                                 glVertex3f(x1, y2, -0.02)
1074                                 glEnd()
1075
1076                 glDisableClientState(GL_VERTEX_ARRAY)
1077                 glDisable(GL_BLEND)
1078                 glDisable(GL_CULL_FACE)
1079
1080         def _generateGCodeVBOs(self, layer):
1081                 ret = []
1082                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1083                         pointList = numpy.zeros((0,3), numpy.float32)
1084                         for path in layer:
1085                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1086                                         a = path['points']
1087                                         a = numpy.concatenate((a[:-1], a[1:]), 1)
1088                                         a = a.reshape((len(a) * 2, 3))
1089                                         pointList = numpy.concatenate((pointList, a))
1090                         ret.append(opengl.GLVBO(pointList))
1091                 return ret
1092
1093         def _generateGCodeVBOs2(self, layer):
1094                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1095                 filamentArea = math.pi * filamentRadius * filamentRadius
1096
1097                 ret = []
1098                 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1099                         pointList = numpy.zeros((0,3), numpy.float32)
1100                         for path in layer:
1101                                 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1102                                         a = path['points']
1103                                         if extrudeType == 'FILL':
1104                                                 a[:,2] += 0.01
1105
1106                                         normal = a[1:] - a[:-1]
1107                                         lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1108                                         normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1109                                         normal[:,2] /= lens
1110
1111                                         ePerDist = path['extrusion'][1:] / lens
1112                                         lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1113
1114                                         normal[:,0] *= lineWidth
1115                                         normal[:,1] *= lineWidth
1116
1117                                         b = numpy.zeros((len(a)-1, 0), numpy.float32)
1118                                         b = numpy.concatenate((b, a[1:] + normal), 1)
1119                                         b = numpy.concatenate((b, a[1:] - normal), 1)
1120                                         b = numpy.concatenate((b, a[:-1] - normal), 1)
1121                                         b = numpy.concatenate((b, a[:-1] + normal), 1)
1122                                         #b = numpy.concatenate((b, a[:-1]), 1)
1123                                         #b = numpy.concatenate((b, a[:-1]), 1)
1124                                         b = b.reshape((len(b) * 4, 3))
1125
1126                                         pointList = numpy.concatenate((pointList, b))
1127                         ret.append(opengl.GLVBO(pointList))
1128
1129                 pointList = numpy.zeros((0,3), numpy.float32)
1130                 for path in layer:
1131                         if path['type'] == 'move' or path['type'] == 'retract':
1132                                 a = path['points']
1133                                 a = numpy.concatenate((a[:-1], a[1:]), 1)
1134                                 a = a.reshape((len(a) * 2, 3))
1135                                 pointList = numpy.concatenate((pointList, a))
1136                 ret.append(opengl.GLVBO(pointList))
1137
1138                 return ret
1139
1140         def getObjectCenterPos(self):
1141                 if self._selectedObj is None:
1142                         return [0.0, 0.0, 0.0]
1143                 pos = self._selectedObj.getPosition()
1144                 size = self._selectedObj.getSize()
1145                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1146
1147         def getObjectBoundaryCircle(self):
1148                 if self._selectedObj is None:
1149                         return 0.0
1150                 return self._selectedObj.getBoundaryCircle()
1151
1152         def getObjectSize(self):
1153                 if self._selectedObj is None:
1154                         return [0.0, 0.0, 0.0]
1155                 return self._selectedObj.getSize()
1156
1157         def getObjectMatrix(self):
1158                 if self._selectedObj is None:
1159                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1160                 return self._selectedObj.getMatrix()
1161
1162 class shaderEditor(wx.Dialog):
1163         def __init__(self, parent, callback, v, f):
1164                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1165                 self._callback = callback
1166                 s = wx.BoxSizer(wx.VERTICAL)
1167                 self.SetSizer(s)
1168                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1169                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1170                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1171                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1172
1173                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1174                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1175
1176                 self.SetPosition(self.GetParent().GetPosition())
1177                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1178                 self.Show()
1179
1180         def OnText(self, e):
1181                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())