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