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