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