chiark / gitweb /
Add object split function. Optimize printing order calculator for shorter runtime.
[cura.git] / Cura / gui / sceneView.py
1 from __future__ import absolute_import
2
3 import wx
4 import numpy
5 import time
6 import os
7 import traceback
8 import shutil
9
10 import OpenGL
11 OpenGL.ERROR_CHECKING = False
12 from OpenGL.GLU import *
13 from OpenGL.GL import *
14
15 from Cura.gui import printWindow
16 from Cura.util import profile
17 from Cura.util import meshLoader
18 from Cura.util import objectScene
19 from Cura.util import resources
20 from Cura.util import sliceEngine
21 from Cura.util import machineCom
22 from Cura.util import removableStorage
23 from Cura.gui.util import previewTools
24 from Cura.gui.util import opengl
25 from Cura.gui.util import openglGui
26
27 class SceneView(openglGui.glGuiPanel):
28         def __init__(self, parent):
29                 super(SceneView, self).__init__(parent)
30
31                 self._yaw = 30
32                 self._pitch = 60
33                 self._zoom = 300
34                 self._scene = objectScene.Scene()
35                 self._objectShader = None
36                 self._focusObj = None
37                 self._selectedObj = None
38                 self._objColors = [None,None,None,None]
39                 self._mouseX = -1
40                 self._mouseY = -1
41                 self._mouseState = None
42                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
43                 self._animView = None
44                 self._animZoom = None
45                 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
46                 self._platformMesh._drawOffset = numpy.array([0,0,1.5], numpy.float32)
47                 self._isSimpleMode = True
48
49                 self._viewport = None
50                 self._modelMatrix = None
51                 self._projMatrix = None
52                 self.tempMatrix = None
53
54                 self.openFileButton      = openglGui.glButton(self, 4, 'Load', (0,0), self.ShowLoadModel)
55                 self.printButton         = openglGui.glButton(self, 6, 'Print', (1,0), self.ShowPrintWindow)
56                 self.printButton.setDisabled(True)
57
58                 group = []
59                 self.rotateToolButton = openglGui.glRadioButton(self, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
60                 self.scaleToolButton  = openglGui.glRadioButton(self, 9, 'Scale', (1,-1), group, self.OnToolSelect)
61                 self.mirrorToolButton  = openglGui.glRadioButton(self, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
62
63                 self.resetRotationButton = openglGui.glButton(self, 12, 'Reset', (0,-2), self.OnRotateReset)
64                 self.layFlatButton       = openglGui.glButton(self, 16, 'Lay flat', (0,-3), self.OnLayFlat)
65
66                 self.resetScaleButton    = openglGui.glButton(self, 13, 'Reset', (1,-2), self.OnScaleReset)
67                 self.scaleMaxButton      = openglGui.glButton(self, 17, 'To max', (1,-3), self.OnScaleMax)
68
69                 self.mirrorXButton       = openglGui.glButton(self, 14, 'Mirror X', (2,-2), lambda button: self.OnMirror(0))
70                 self.mirrorYButton       = openglGui.glButton(self, 18, 'Mirror Y', (2,-3), lambda button: self.OnMirror(1))
71                 self.mirrorZButton       = openglGui.glButton(self, 22, 'Mirror Z', (2,-4), lambda button: self.OnMirror(2))
72
73                 self.rotateToolButton.setExpandArrow(True)
74                 self.scaleToolButton.setExpandArrow(True)
75                 self.mirrorToolButton.setExpandArrow(True)
76
77                 self.scaleForm = openglGui.glFrame(self, (2, -2))
78                 openglGui.glGuiLayoutGrid(self.scaleForm)
79                 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
80                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
81                 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
82                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
83                 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
84                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
85                 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
86                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
87                 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
88                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
89                 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
90                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
91                 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
92                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
93
94                 self.notification = openglGui.glNotification(self, (0, 0))
95
96                 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
97                 self._sceneUpdateTimer = wx.Timer(self)
98                 self.Bind(wx.EVT_TIMER, lambda e : self._slicer.runSlicer(self._scene), self._sceneUpdateTimer)
99                 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
100
101                 self.OnToolSelect(0)
102                 self.updateToolButtons()
103                 self.updateProfileToControls()
104
105         def ShowLoadModel(self, button):
106                 if button == 1:
107                         dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
108                         dlg.SetWildcard(meshLoader.wildcardFilter())
109                         if dlg.ShowModal() != wx.ID_OK:
110                                 dlg.Destroy()
111                                 return
112                         filename = dlg.GetPath()
113                         dlg.Destroy()
114                         if not(os.path.exists(filename)):
115                                 return False
116                         profile.putPreference('lastFile', filename)
117                         self.GetParent().GetParent().GetParent().addToModelMRU(filename)
118                         self.loadScene([filename])
119
120         def ShowPrintWindow(self, button):
121                 if button == 1:
122                         if machineCom.machineIsConnected():
123                                 printWindow.printFile(self._slicer.getGCodeFilename())
124                         elif len(removableStorage.getPossibleSDcardDrives()) > 0:
125                                 drives = removableStorage.getPossibleSDcardDrives()
126                                 if len(drives) > 1:
127                                         drive = drives[0]
128                                 else:
129                                         drive = drives[0]
130                                 filename = os.path.basename(profile.getPreference('lastFile'))
131                                 filename = filename[0:filename.rfind('.')] + '.gcode'
132                                 shutil.copy(self._slicer.getGCodeFilename(), drive[1] + filename)
133                         else:
134                                 defPath = profile.getPreference('lastFile')
135                                 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
136                                 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
137                                 dlg.SetFilename(defPath)
138                                 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
139                                 if dlg.ShowModal() != wx.ID_OK:
140                                         dlg.Destroy()
141                                         return
142                                 filename = dlg.GetPath()
143                                 dlg.Destroy()
144
145                                 shutil.copy(self._slicer.getGCodeFilename(), filename)
146
147         def OnToolSelect(self, button):
148                 if self.rotateToolButton.getSelected():
149                         self.tool = previewTools.toolRotate(self)
150                 elif self.scaleToolButton.getSelected():
151                         self.tool = previewTools.toolScale(self)
152                 elif self.mirrorToolButton.getSelected():
153                         self.tool = previewTools.toolNone(self)
154                 else:
155                         self.tool = previewTools.toolNone(self)
156                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
157                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
158                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
159                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
160                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
161                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
162                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
163                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
164
165         def updateToolButtons(self):
166                 if self._selectedObj is None:
167                         hidden = True
168                 else:
169                         hidden = False
170                 self.rotateToolButton.setHidden(hidden)
171                 self.scaleToolButton.setHidden(hidden)
172                 self.mirrorToolButton.setHidden(hidden)
173                 if hidden:
174                         self.rotateToolButton.setSelected(False)
175                         self.scaleToolButton.setSelected(False)
176                         self.mirrorToolButton.setSelected(False)
177                         self.OnToolSelect(0)
178
179         def OnRotateReset(self, button):
180                 if self._selectedObj is None:
181                         return
182                 self._selectedObj.resetRotation()
183
184         def OnLayFlat(self, button):
185                 if self._selectedObj is None:
186                         return
187                 self._selectedObj.layFlat()
188
189         def OnScaleReset(self, button):
190                 if self._selectedObj is None:
191                         return
192                 self._selectedObj.resetScale()
193
194         def OnScaleMax(self, button):
195                 if self._selectedObj is None:
196                         return
197                 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
198
199         def OnMirror(self, axis):
200                 if self._selectedObj is None:
201                         return
202                 self._selectedObj.mirror(axis)
203                 self.sceneUpdated()
204
205         def OnScaleEntry(self, value, axis):
206                 if self._selectedObj is None:
207                         return
208                 try:
209                         value = float(value)
210                 except:
211                         return
212                 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
213                 self.updateProfileToControls()
214                 self.sceneUpdated()
215
216         def OnScaleEntryMM(self, value, axis):
217                 if self._selectedObj is None:
218                         return
219                 try:
220                         value = float(value)
221                 except:
222                         return
223                 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
224                 self.updateProfileToControls()
225                 self.sceneUpdated()
226
227         def OnDuplicateObject(self, e):
228                 if self._selectedObj is None:
229                         return
230                 self._scene.add(self._selectedObj.copy())
231                 self._scene.centerAll()
232                 self.sceneUpdated()
233
234         def OnSplitObject(self, e):
235                 if self._selectedObj is None:
236                         return
237                 self._scene.remove(self._selectedObj)
238                 for obj in self._selectedObj.split():
239                         self._scene.add(obj)
240                 self._scene.centerAll()
241                 self._selectObject(None)
242                 self.sceneUpdated()
243
244         def OnMergeObjects(self, e):
245                 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
246                         return
247                 self._scene.merge(self._selectedObj, self._focusObj)
248                 self.sceneUpdated()
249
250         def sceneUpdated(self):
251                 self._sceneUpdateTimer.Start(1, True)
252                 self._slicer.abortSlicer()
253                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
254                 self.Refresh()
255
256         def _updateSliceProgress(self, progressValue, ready):
257                 self.printButton.setDisabled(not ready)
258                 self.printButton.setProgressBar(progressValue)
259                 self.Refresh()
260
261         def loadScene(self, fileList):
262                 for filename in fileList:
263                         try:
264                                 objList = meshLoader.loadMeshes(filename)
265                         except:
266                                 traceback.print_exc()
267                         else:
268                                 for obj in objList:
269                                         obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
270                                         self._scene.add(obj)
271                                         self._scene.centerAll()
272                                         self._selectObject(obj)
273                 self.sceneUpdated()
274
275         def _deleteObject(self, obj):
276                 if obj == self._selectedObj:
277                         self._selectObject(None)
278                 if obj == self._focusObj:
279                         self._focusObj = None
280                 self._scene.remove(obj)
281                 for m in obj._meshList:
282                         if m.vbo is not None and m.vbo.decRef():
283                                 self.glReleaseList.append(m.vbo)
284                 if self._isSimpleMode:
285                         self._scene.arrangeAll()
286                 self.sceneUpdated()
287
288         def _selectObject(self, obj, zoom = True):
289                 if obj != self._selectedObj:
290                         self._selectedObj = obj
291                         self.updateProfileToControls()
292                         self.updateToolButtons()
293                 if zoom and obj is not None:
294                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
295                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
296                         newZoom = obj.getBoundaryCircle() * 6
297                         if newZoom > numpy.max(self._machineSize) * 3:
298                                 newZoom = numpy.max(self._machineSize) * 3
299                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
300
301         def updateProfileToControls(self):
302                 oldSimpleMode = self._isSimpleMode
303                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
304                 if self._isSimpleMode and not oldSimpleMode:
305                         self._scene.arrangeAll()
306                         self.sceneUpdated()
307                 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
308                 self._objColors[0] = profile.getPreferenceColour('model_colour')
309                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
310                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
311                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
312                 self._scene.setMachineSize(self._machineSize)
313                 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
314                 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'))
315
316                 if self._selectedObj is not None:
317                         scale = self._selectedObj.getScale()
318                         size = self._selectedObj.getSize()
319                         self.scaleXctrl.setValue(round(scale[0], 2))
320                         self.scaleYctrl.setValue(round(scale[1], 2))
321                         self.scaleZctrl.setValue(round(scale[2], 2))
322                         self.scaleXmmctrl.setValue(round(size[0], 2))
323                         self.scaleYmmctrl.setValue(round(size[1], 2))
324                         self.scaleZmmctrl.setValue(round(size[2], 2))
325
326         def OnKeyChar(self, keyCode):
327                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
328                         if self._selectedObj is not None:
329                                 self._deleteObject(self._selectedObj)
330                                 self.Refresh()
331
332                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
333                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
334
335         def ShaderUpdate(self, v, f):
336                 s = opengl.GLShader(v, f)
337                 if s.isValid():
338                         self._objectLoadShader.release()
339                         self._objectLoadShader = s
340                         for obj in self._scene.objects():
341                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
342                         self.Refresh()
343
344         def OnMouseDown(self,e):
345                 self._mouseX = e.GetX()
346                 self._mouseY = e.GetY()
347                 self._mouseClick3DPos = self._mouse3Dpos
348                 self._mouseClickFocus = self._focusObj
349                 if e.ButtonDClick():
350                         self._mouseState = 'doubleClick'
351                 else:
352                         self._mouseState = 'dragOrClick'
353                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
354                 p0 -= self.getObjectCenterPos() - self._viewTarget
355                 p1 -= self.getObjectCenterPos() - self._viewTarget
356                 if self.tool.OnDragStart(p0, p1):
357                         self._mouseState = 'tool'
358                 if self._mouseState == 'dragOrClick':
359                         if e.GetButton() == 1:
360                                 if self._focusObj is not None:
361                                         self._selectObject(self._focusObj, False)
362                                         self.Refresh()
363
364         def OnMouseUp(self, e):
365                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
366                         return
367                 if self._mouseState == 'dragOrClick':
368                         if e.GetButton() == 1:
369                                 self._selectObject(self._focusObj)
370                         if e.GetButton() == 3:
371                                 if self._selectedObj is not None:
372                                         menu = wx.Menu()
373                                         self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._selectedObj), menu.Append(-1, 'Delete'))
374                                         if self._selectedObj == self._focusObj:
375                                                 self.Bind(wx.EVT_MENU, self.OnDuplicateObject, menu.Append(-1, 'Duplicate'))
376                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
377                                         if self._selectedObj != self._focusObj and self._focusObj is not None:
378                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
379                                         if menu.MenuItemCount > 0:
380                                                 self.PopupMenu(menu)
381                                         menu.Destroy()
382                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
383                         self._scene.pushFree()
384                         self.sceneUpdated()
385                 elif self._mouseState == 'tool':
386                         if self.tempMatrix is not None and self._selectedObj is not None:
387                                 self._selectedObj.applyMatrix(self.tempMatrix)
388                         self.tempMatrix = None
389                         self.tool.OnDragEnd()
390                         self.sceneUpdated()
391                 self._mouseState = None
392
393         def OnMouseMotion(self,e):
394                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
395                 p0 -= self.getObjectCenterPos() - self._viewTarget
396                 p1 -= self.getObjectCenterPos() - self._viewTarget
397
398                 if e.Dragging() and self._mouseState is not None:
399                         if self._mouseState == 'tool':
400                                 self.tool.OnDrag(p0, p1)
401                         elif not e.LeftIsDown() and e.RightIsDown():
402                                 self._mouseState = 'drag'
403                                 self._yaw += e.GetX() - self._mouseX
404                                 self._pitch -= e.GetY() - self._mouseY
405                                 if self._pitch > 170:
406                                         self._pitch = 170
407                                 if self._pitch < 10:
408                                         self._pitch = 10
409                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
410                                 self._mouseState = 'drag'
411                                 self._zoom += e.GetY() - self._mouseY
412                                 if self._zoom < 1:
413                                         self._zoom = 1
414                                 if self._zoom > numpy.max(self._machineSize) * 3:
415                                         self._zoom = numpy.max(self._machineSize) * 3
416                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
417                                 self._mouseState = 'dragObject'
418                                 z = max(0, self._mouseClick3DPos[2])
419                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
420                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
421                                 p0[2] -= z
422                                 p1[2] -= z
423                                 p2[2] -= z
424                                 p3[2] -= z
425                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
426                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
427                                 diff = cursorZ1 - cursorZ0
428                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
429                 if not e.Dragging() or self._mouseState != 'tool':
430                         self.tool.OnMouseMove(p0, p1)
431
432                 self._mouseX = e.GetX()
433                 self._mouseY = e.GetY()
434
435         def OnMouseWheel(self, e):
436                 self._zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
437                 if self._zoom < 1.0:
438                         self._zoom = 1.0
439                 if self._zoom > numpy.max(self._machineSize) * 3:
440                         self._zoom = numpy.max(self._machineSize) * 3
441                 self.Refresh()
442
443         def getMouseRay(self, x, y):
444                 if self._viewport is None:
445                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
446                 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
447                 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
448                 p0 -= self._viewTarget
449                 p1 -= self._viewTarget
450                 return p0, p1
451
452         def _init3DView(self):
453                 # set viewing projection
454                 size = self.GetSize()
455                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
456                 glLoadIdentity()
457
458                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
459
460                 glDisable(GL_RESCALE_NORMAL)
461                 glDisable(GL_LIGHTING)
462                 glDisable(GL_LIGHT0)
463                 glEnable(GL_DEPTH_TEST)
464                 glDisable(GL_CULL_FACE)
465                 glDisable(GL_BLEND)
466                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
467
468                 glClearColor(0.8, 0.8, 0.8, 1.0)
469                 glClearStencil(0)
470                 glClearDepth(1.0)
471
472                 glMatrixMode(GL_PROJECTION)
473                 glLoadIdentity()
474                 aspect = float(size.GetWidth()) / float(size.GetHeight())
475                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
476
477                 glMatrixMode(GL_MODELVIEW)
478                 glLoadIdentity()
479                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
480
481         def OnPaint(self,e):
482                 if machineCom.machineIsConnected():
483                         self.printButton._imageID = 6
484                         self.printButton._tooltip = 'Print'
485                 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
486                         self.printButton._imageID = 2
487                         self.printButton._tooltip = 'Toolpath to SD'
488                 else:
489                         self.printButton._imageID = 3
490                         self.printButton._tooltip = 'Save toolpath'
491
492                 if self._animView is not None:
493                         self._viewTarget = self._animView.getPosition()
494                         if self._animView.isDone():
495                                 self._animView = None
496                 if self._animZoom is not None:
497                         self._zoom = self._animZoom.getPosition()
498                         if self._animZoom.isDone():
499                                 self._animZoom = None
500                 if self._objectShader is None:
501                         self._objectShader = opengl.GLShader("""
502 varying float light_amount;
503
504 void main(void)
505 {
506     gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
507     gl_FrontColor = gl_Color;
508
509         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
510         light_amount += 0.2;
511 }
512                         ""","""
513 varying float light_amount;
514
515 void main(void)
516 {
517         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
518 }
519                         """)
520                         self._objectLoadShader = opengl.GLShader("""
521 uniform float intensity;
522 uniform float scale;
523 varying float light_amount;
524
525 void main(void)
526 {
527         vec4 tmp = gl_Vertex;
528     tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
529     tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
530     gl_Position = gl_ModelViewProjectionMatrix * tmp;
531     gl_FrontColor = gl_Color;
532
533         light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
534         light_amount += 0.2;
535 }
536                         ""","""
537 uniform float intensity;
538 varying float light_amount;
539
540 void main(void)
541 {
542         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
543 }
544                         """)
545                 self._init3DView()
546                 glTranslate(0,0,-self._zoom)
547                 glRotate(-self._pitch, 1,0,0)
548                 glRotate(self._yaw, 0,0,1)
549                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
550
551                 self._viewport = glGetIntegerv(GL_VIEWPORT)
552                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
553                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
554
555                 glClearColor(1,1,1,1)
556                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
557
558                 for n in xrange(0, len(self._scene.objects())):
559                         obj = self._scene.objects()[n]
560                         glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
561                         self._renderObject(obj)
562
563                 if self._mouseX > -1:
564                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
565                         if n < len(self._scene.objects()):
566                                 self._focusObj = self._scene.objects()[n]
567                         else:
568                                 self._focusObj = None
569                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
570                         self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
571                         self._mouse3Dpos -= self._viewTarget
572
573                 self._init3DView()
574                 glTranslate(0,0,-self._zoom)
575                 glRotate(-self._pitch, 1,0,0)
576                 glRotate(self._yaw, 0,0,1)
577                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
578
579                 glStencilFunc(GL_ALWAYS, 1, 1)
580                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
581                 self._objectShader.bind()
582                 for obj in self._scene.objects():
583                         if obj._loadAnim is not None:
584                                 if obj._loadAnim.isDone():
585                                         obj._loadAnim = None
586                                 else:
587                                         continue
588                         brightness = 1.0
589                         glDisable(GL_STENCIL_TEST)
590                         if self._selectedObj == obj:
591                                 glEnable(GL_STENCIL_TEST)
592                         if self._focusObj == obj:
593                                 brightness = 1.2
594                         elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
595                                 brightness = 0.8
596                         if not self._scene.checkPlatform(obj):
597                                 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
598                                 self._renderObject(obj)
599                         else:
600                                 self._renderObject(obj, brightness)
601                 self._objectShader.unbind()
602
603                 glDisable(GL_STENCIL_TEST)
604                 glEnable(GL_BLEND)
605                 self._objectLoadShader.bind()
606                 glColor4f(0.2, 0.6, 1.0, 1.0)
607                 for obj in self._scene.objects():
608                         if obj._loadAnim is None:
609                                 continue
610                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
611                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
612                         self._renderObject(obj)
613                 self._objectLoadShader.unbind()
614                 glDisable(GL_BLEND)
615
616                 self._drawMachine()
617
618                 #Draw the object box-shadow, so you can see where it will collide with other objects.
619                 if self._selectedObj is not None and len(self._scene.objects()) > 1:
620                         size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
621                         glPushMatrix()
622                         glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
623                         glEnable(GL_BLEND)
624                         glEnable(GL_CULL_FACE)
625                         glColor4f(0,0,0,0.12)
626                         glBegin(GL_QUADS)
627                         glVertex3f(-size[0],  size[1], 0.1)
628                         glVertex3f(-size[0], -size[1], 0.1)
629                         glVertex3f( size[0], -size[1], 0.1)
630                         glVertex3f( size[0],  size[1], 0.1)
631                         glEnd()
632                         glDisable(GL_CULL_FACE)
633                         glPopMatrix()
634
635                 #Draw the outline of the selected object, on top of everything else except the GUI.
636                 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
637                         glDisable(GL_DEPTH_TEST)
638                         glEnable(GL_CULL_FACE)
639                         glEnable(GL_STENCIL_TEST)
640                         glDisable(GL_BLEND)
641                         glStencilFunc(GL_EQUAL, 0, 255)
642
643                         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
644                         glLineWidth(2)
645                         glColor4f(1,1,1,0.5)
646                         self._renderObject(self._selectedObj)
647                         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
648
649                         glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
650                         glDisable(GL_STENCIL_TEST)
651                         glDisable(GL_CULL_FACE)
652                         glEnable(GL_DEPTH_TEST)
653
654                 if self._selectedObj is not None:
655                         glPushMatrix()
656                         pos = self.getObjectCenterPos()
657                         glTranslate(pos[0], pos[1], pos[2])
658                         self.tool.OnDraw()
659                         glPopMatrix()
660
661         def _renderObject(self, obj, brightness = False):
662                 glPushMatrix()
663                 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
664
665                 if self.tempMatrix is not None and obj == self._selectedObj:
666                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
667                         glMultMatrixf(tempMatrix)
668
669                 offset = obj.getDrawOffset()
670                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
671
672                 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
673                 glMultMatrixf(tempMatrix)
674
675                 n = 0
676                 for m in obj._meshList:
677                         if m.vbo is None:
678                                 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
679                         if brightness:
680                                 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
681                                 n += 1
682                         m.vbo.render()
683                 glPopMatrix()
684
685         def _drawMachine(self):
686                 glEnable(GL_CULL_FACE)
687                 glEnable(GL_BLEND)
688
689                 if profile.getPreference('machine_type') == 'ultimaker':
690                         glColor4f(1,1,1,0.5)
691                         self._objectShader.bind()
692                         self._renderObject(self._platformMesh)
693                         self._objectShader.unbind()
694
695                 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
696                 v0 = [ size[0] / 2, size[1] / 2, size[2]]
697                 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
698                 v2 = [-size[0] / 2, size[1] / 2, size[2]]
699                 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
700                 v4 = [ size[0] / 2, size[1] / 2, 0]
701                 v5 = [ size[0] / 2,-size[1] / 2, 0]
702                 v6 = [-size[0] / 2, size[1] / 2, 0]
703                 v7 = [-size[0] / 2,-size[1] / 2, 0]
704
705                 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
706                 glEnableClientState(GL_VERTEX_ARRAY)
707                 glVertexPointer(3, GL_FLOAT, 3*4, vList)
708
709                 glColor4ub(5, 171, 231, 64)
710                 glDrawArrays(GL_QUADS, 0, 4)
711                 glColor4ub(5, 171, 231, 96)
712                 glDrawArrays(GL_QUADS, 4, 8)
713                 glColor4ub(5, 171, 231, 128)
714                 glDrawArrays(GL_QUADS, 12, 8)
715
716                 sx = self._machineSize[0]
717                 sy = self._machineSize[1]
718                 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
719                         for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
720                                 x1 = x * 10
721                                 x2 = x1 + 10
722                                 y1 = y * 10
723                                 y2 = y1 + 10
724                                 x1 = max(min(x1, sx/2), -sx/2)
725                                 y1 = max(min(y1, sy/2), -sy/2)
726                                 x2 = max(min(x2, sx/2), -sx/2)
727                                 y2 = max(min(y2, sy/2), -sy/2)
728                                 if (x & 1) == (y & 1):
729                                         glColor4ub(5, 171, 231, 127)
730                                 else:
731                                         glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
732                                 glBegin(GL_QUADS)
733                                 glVertex3f(x1, y1, -0.02)
734                                 glVertex3f(x2, y1, -0.02)
735                                 glVertex3f(x2, y2, -0.02)
736                                 glVertex3f(x1, y2, -0.02)
737                                 glEnd()
738
739                 glDisableClientState(GL_VERTEX_ARRAY)
740                 glDisable(GL_BLEND)
741                 glDisable(GL_CULL_FACE)
742
743         def getObjectCenterPos(self):
744                 if self._selectedObj is None:
745                         return [0.0, 0.0, 0.0]
746                 pos = self._selectedObj.getPosition()
747                 size = self._selectedObj.getSize()
748                 return [pos[0], pos[1], size[2]/2]
749
750         def getObjectBoundaryCircle(self):
751                 if self._selectedObj is None:
752                         return 0.0
753                 return self._selectedObj.getBoundaryCircle()
754
755         def getObjectSize(self):
756                 if self._selectedObj is None:
757                         return [0.0, 0.0, 0.0]
758                 return self._selectedObj.getSize()
759
760         def getObjectMatrix(self):
761                 if self._selectedObj is None:
762                         return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
763                 return self._selectedObj.getMatrix()
764
765 class shaderEditor(wx.Dialog):
766         def __init__(self, parent, callback, v, f):
767                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
768                 self._callback = callback
769                 s = wx.BoxSizer(wx.VERTICAL)
770                 self.SetSizer(s)
771                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
772                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
773                 s.Add(self._vertex, 1, flag=wx.EXPAND)
774                 s.Add(self._fragment, 1, flag=wx.EXPAND)
775
776                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
777                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
778
779                 self.SetPosition(self.GetParent().GetPosition())
780                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
781                 self.Show()
782
783         def OnText(self, e):
784                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())