1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
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
31 class SceneView(openglGui.glGuiPanel):
32 def __init__(self, parent):
33 super(SceneView, self).__init__(parent)
38 self._scene = objectScene.Scene()
41 self._objectShader = None
43 self._selectedObj = None
44 self._objColors = [None,None,None,None]
47 self._mouseState = None
48 self._viewTarget = numpy.array([0,0,0], numpy.float32)
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
56 self._modelMatrix = None
57 self._projMatrix = None
58 self.tempMatrix = None
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)
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)
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)
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)
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))
79 self.rotateToolButton.setExpandArrow(True)
80 self.scaleToolButton.setExpandArrow(True)
81 self.mirrorToolButton.setExpandArrow(True)
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)
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())
103 self.notification = openglGui.glNotification(self, (0, 0))
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)
112 self.updateToolButtons()
113 self.updateProfileToControls()
115 def showLoadModel(self, button = 1):
117 dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
118 dlg.SetWildcard(meshLoader.loadWildcardFilter() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
119 if dlg.ShowModal() != wx.ID_OK:
122 filename = dlg.GetPath()
124 if not(os.path.exists(filename)):
126 profile.putPreference('lastFile', filename)
127 self.GetParent().GetParent().GetParent().addToModelMRU(filename)
128 ext = filename[filename.rfind('.')+1:].upper()
129 if ext == 'G' or ext == 'GCODE':
130 if self._gcode is not None:
132 for layerVBOlist in self._gcodeVBOs:
133 for vbo in layerVBOlist:
134 self.glReleaseList.append(vbo)
136 self._gcode = gcodeInterpreter.gcode()
137 self._gcode.progressCallback = self._gcodeLoadCallback
138 self._thread = threading.Thread(target=self._loadGCode, args=(filename,))
139 self._thread.daemon = True
141 self.printButton.setBottomText('')
142 self.viewSelection.setValue(4)
143 self.printButton.setDisabled(False)
146 if self.viewSelection.getValue() == 4:
147 self.viewSelection.setValue(0)
149 self.loadScene([filename])
151 def showSaveModel(self):
152 if len(self._scene.objects()) < 1:
154 dlg=wx.FileDialog(self, 'Save 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
155 dlg.SetWildcard(meshLoader.saveWildcardFilter())
156 if dlg.ShowModal() != wx.ID_OK:
159 filename = dlg.GetPath()
161 meshLoader.saveMeshes(filename, self._scene.objects())
163 def showPrintWindow(self, button):
165 if machineCom.machineIsConnected():
166 printWindow.printFile(self._gcode.filename)
167 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
168 drives = removableStorage.getPossibleSDcardDrives()
170 dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
171 if dlg.ShowModal() != wx.ID_OK:
174 drive = drives[dlg.GetSelection()]
178 filename = os.path.basename(profile.getPreference('lastFile'))
179 filename = filename[0:filename.rfind('.')] + '.gcode'
180 threading.Thread(target=self._copyFile,args=(self._gcode.filename, drive[1] + filename, drive[1])).start()
185 self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._gcode.filename), menu.Append(-1, 'Print with USB'))
186 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, 'Save GCode...'))
187 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
191 def showSaveGCode(self):
192 defPath = profile.getPreference('lastFile')
193 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
194 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
195 dlg.SetFilename(os.path.basename(defPath))
196 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
197 if dlg.ShowModal() != wx.ID_OK:
200 filename = dlg.GetPath()
203 threading.Thread(target=self._copyFile,args=(self._gcode.filename, filename)).start()
205 def _copyFile(self, fileA, fileB, allowEject = False):
207 size = float(os.stat(fileA).st_size)
208 with open(fileA, 'rb') as fsrc:
209 with open(fileB, 'wb') as fdst:
211 buf = fsrc.read(16*1024)
215 self.printButton.setProgressBar(float(fsrc.tell()) / size)
218 self.notification.message("Failed to save")
221 self.notification.message("Saved as %s" % (fileB), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(allowEject) else self.notification.message('Safe remove failed...'))
223 self.notification.message("Saved as %s" % (fileB))
224 self.printButton.setProgressBar(None)
227 def _showSliceLog(self):
228 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
232 def OnToolSelect(self, button):
233 if self.rotateToolButton.getSelected():
234 self.tool = previewTools.toolRotate(self)
235 elif self.scaleToolButton.getSelected():
236 self.tool = previewTools.toolScale(self)
237 elif self.mirrorToolButton.getSelected():
238 self.tool = previewTools.toolNone(self)
240 self.tool = previewTools.toolNone(self)
241 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
242 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
243 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
244 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
245 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
246 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
247 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
248 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
250 def updateToolButtons(self):
251 if self._selectedObj is None:
255 self.rotateToolButton.setHidden(hidden)
256 self.scaleToolButton.setHidden(hidden)
257 self.mirrorToolButton.setHidden(hidden)
259 self.rotateToolButton.setSelected(False)
260 self.scaleToolButton.setSelected(False)
261 self.mirrorToolButton.setSelected(False)
264 def OnViewChange(self):
265 if self.viewSelection.getValue() == 4:
266 self.viewMode = 'gcode'
267 if self._gcode is not None:
268 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
269 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
270 self._selectObject(None)
271 elif self.viewSelection.getValue() == 1:
272 self.viewMode = 'overhang'
273 elif self.viewSelection.getValue() == 2:
274 self.viewMode = 'transparent'
275 elif self.viewSelection.getValue() == 3:
276 self.viewMode = 'xray'
278 self.viewMode = 'normal'
279 self.layerSelect.setHidden(self.viewMode != 'gcode')
282 def OnRotateReset(self, button):
283 if self._selectedObj is None:
285 self._selectedObj.resetRotation()
286 self._scene.pushFree()
287 self._selectObject(self._selectedObj)
290 def OnLayFlat(self, button):
291 if self._selectedObj is None:
293 self._selectedObj.layFlat()
294 self._scene.pushFree()
295 self._selectObject(self._selectedObj)
298 def OnScaleReset(self, button):
299 if self._selectedObj is None:
301 self._selectedObj.resetScale()
302 self._selectObject(self._selectedObj)
303 self.updateProfileToControls()
306 def OnScaleMax(self, button):
307 if self._selectedObj is None:
309 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
310 self._scene.pushFree()
311 self._selectObject(self._selectedObj)
312 self.updateProfileToControls()
315 def OnMirror(self, axis):
316 if self._selectedObj is None:
318 self._selectedObj.mirror(axis)
321 def OnScaleEntry(self, value, axis):
322 if self._selectedObj is None:
328 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
329 self.updateProfileToControls()
330 self._scene.pushFree()
331 self._selectObject(self._selectedObj)
334 def OnScaleEntryMM(self, value, axis):
335 if self._selectedObj is None:
341 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
342 self.updateProfileToControls()
343 self._scene.pushFree()
344 self._selectObject(self._selectedObj)
347 def OnDeleteAll(self, e):
348 while len(self._scene.objects()) > 0:
349 self._deleteObject(self._scene.objects()[0])
350 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
352 def OnMultiply(self, e):
353 if self._focusObj is None:
356 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
357 if dlg.ShowModal() != wx.ID_OK:
360 cnt = dlg.GetValue() - 1
366 self._scene.add(newObj)
367 self._scene.centerAll()
368 if not self._scene.checkPlatform(newObj):
373 self.notification.message("Could not create more then %d items" % (n))
374 self._scene.remove(newObj)
375 self._scene.centerAll()
378 def OnSplitObject(self, e):
379 if self._focusObj is None:
381 self._scene.remove(self._focusObj)
382 for obj in self._focusObj.split(self._splitCallback):
384 self._scene.centerAll()
385 self._selectObject(None)
388 def _splitCallback(self, progress):
391 def OnMergeObjects(self, e):
392 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
394 self._scene.merge(self._selectedObj, self._focusObj)
397 def sceneUpdated(self):
398 self._sceneUpdateTimer.Start(1, True)
399 self._slicer.abortSlicer()
400 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
403 def _onRunSlicer(self, e):
404 if self._isSimpleMode:
405 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
406 self._slicer.runSlicer(self._scene)
407 if self._isSimpleMode:
408 profile.resetTempOverride()
410 def _updateSliceProgress(self, progressValue, ready):
411 self.printButton.setDisabled(not ready)
412 if progressValue >= 0.0:
413 self.printButton.setProgressBar(progressValue)
415 self.printButton.setProgressBar(None)
416 if self._gcode is not None:
418 for layerVBOlist in self._gcodeVBOs:
419 for vbo in layerVBOlist:
420 self.glReleaseList.append(vbo)
423 self.printButton.setProgressBar(None)
424 cost = self._slicer.getFilamentCost()
426 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
428 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
429 self._gcode = gcodeInterpreter.gcode()
430 self._gcode.progressCallback = self._gcodeLoadCallback
431 self._thread = threading.Thread(target=self._loadGCode)
432 self._thread.daemon = True
435 self.printButton.setBottomText('')
438 def _loadGCode(self, filename = None):
440 filename = self._slicer.getGCodeFilename()
441 self._gcode.load(filename)
443 def _gcodeLoadCallback(self, progress):
444 if self._gcode is None:
446 if len(self._gcode.layerList) % 5 == 0:
448 if self._gcode is None:
450 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
451 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
452 self.layerSelect.setValue(self.layerSelect.getMaxValue())
454 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
455 if self.viewMode == 'gcode':
459 def loadScene(self, fileList):
460 for filename in fileList:
462 objList = meshLoader.loadMeshes(filename)
464 traceback.print_exc()
467 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
469 self._scene.centerAll()
470 self._selectObject(obj)
473 def _deleteObject(self, obj):
474 if obj == self._selectedObj:
475 self._selectObject(None)
476 if obj == self._focusObj:
477 self._focusObj = None
478 self._scene.remove(obj)
479 for m in obj._meshList:
480 if m.vbo is not None and m.vbo.decRef():
481 self.glReleaseList.append(m.vbo)
486 def _selectObject(self, obj, zoom = True):
487 if obj != self._selectedObj:
488 self._selectedObj = obj
489 self.updateProfileToControls()
490 self.updateToolButtons()
491 if zoom and obj is not None:
492 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
493 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
494 newZoom = obj.getBoundaryCircle() * 6
495 if newZoom > numpy.max(self._machineSize) * 3:
496 newZoom = numpy.max(self._machineSize) * 3
497 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
499 def updateProfileToControls(self):
500 oldSimpleMode = self._isSimpleMode
501 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
502 if self._isSimpleMode and not oldSimpleMode:
503 self._scene.arrangeAll()
505 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
506 self._objColors[0] = profile.getPreferenceColour('model_colour')
507 self._objColors[1] = profile.getPreferenceColour('model_colour2')
508 self._objColors[2] = profile.getPreferenceColour('model_colour3')
509 self._objColors[3] = profile.getPreferenceColour('model_colour4')
510 self._scene.setMachineSize(self._machineSize)
511 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
512 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'))
514 if self._selectedObj is not None:
515 scale = self._selectedObj.getScale()
516 size = self._selectedObj.getSize()
517 self.scaleXctrl.setValue(round(scale[0], 2))
518 self.scaleYctrl.setValue(round(scale[1], 2))
519 self.scaleZctrl.setValue(round(scale[2], 2))
520 self.scaleXmmctrl.setValue(round(size[0], 2))
521 self.scaleYmmctrl.setValue(round(size[1], 2))
522 self.scaleZmmctrl.setValue(round(size[2], 2))
524 def OnKeyChar(self, keyCode):
525 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
526 if self._selectedObj is not None:
527 self._deleteObject(self._selectedObj)
529 if keyCode == wx.WXK_UP:
530 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
532 elif keyCode == wx.WXK_DOWN:
533 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
535 elif keyCode == wx.WXK_PAGEUP:
536 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
538 elif keyCode == wx.WXK_PAGEDOWN:
539 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
542 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
543 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
544 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
545 from collections import defaultdict
546 from gc import get_objects
547 self._beforeLeakTest = defaultdict(int)
548 for i in get_objects():
549 self._beforeLeakTest[type(i)] += 1
550 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
551 from collections import defaultdict
552 from gc import get_objects
553 self._afterLeakTest = defaultdict(int)
554 for i in get_objects():
555 self._afterLeakTest[type(i)] += 1
556 for k in self._afterLeakTest:
557 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
558 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
560 def ShaderUpdate(self, v, f):
561 s = opengl.GLShader(v, f)
563 self._objectLoadShader.release()
564 self._objectLoadShader = s
565 for obj in self._scene.objects():
566 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
569 def OnMouseDown(self,e):
570 self._mouseX = e.GetX()
571 self._mouseY = e.GetY()
572 self._mouseClick3DPos = self._mouse3Dpos
573 self._mouseClickFocus = self._focusObj
575 self._mouseState = 'doubleClick'
577 self._mouseState = 'dragOrClick'
578 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
579 p0 -= self.getObjectCenterPos() - self._viewTarget
580 p1 -= self.getObjectCenterPos() - self._viewTarget
581 if self.tool.OnDragStart(p0, p1):
582 self._mouseState = 'tool'
583 if self._mouseState == 'dragOrClick':
584 if e.GetButton() == 1:
585 if self._focusObj is not None:
586 self._selectObject(self._focusObj, False)
589 def OnMouseUp(self, e):
590 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
592 if self._mouseState == 'dragOrClick':
593 if e.GetButton() == 1:
594 self._selectObject(self._focusObj)
595 if e.GetButton() == 3:
597 if self._focusObj is not None:
598 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
599 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
600 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
601 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
602 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
603 if len(self._scene.objects()) > 0:
604 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
605 if menu.MenuItemCount > 0:
608 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
609 self._scene.pushFree()
611 elif self._mouseState == 'tool':
612 if self.tempMatrix is not None and self._selectedObj is not None:
613 self._selectedObj.applyMatrix(self.tempMatrix)
614 self._scene.pushFree()
615 self._selectObject(self._selectedObj)
616 self.tempMatrix = None
617 self.tool.OnDragEnd()
619 self._mouseState = None
621 def OnMouseMotion(self,e):
622 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
623 p0 -= self.getObjectCenterPos() - self._viewTarget
624 p1 -= self.getObjectCenterPos() - self._viewTarget
626 if e.Dragging() and self._mouseState is not None:
627 if self._mouseState == 'tool':
628 self.tool.OnDrag(p0, p1)
629 elif not e.LeftIsDown() and e.RightIsDown():
630 self._mouseState = 'drag'
631 if wx.GetKeyState(wx.WXK_SHIFT):
632 a = math.cos(math.radians(self._yaw)) / 3.0
633 b = math.sin(math.radians(self._yaw)) / 3.0
634 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
635 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
636 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
637 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
639 self._yaw += e.GetX() - self._mouseX
640 self._pitch -= e.GetY() - self._mouseY
641 if self._pitch > 170:
645 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
646 self._mouseState = 'drag'
647 self._zoom += e.GetY() - self._mouseY
650 if self._zoom > numpy.max(self._machineSize) * 3:
651 self._zoom = numpy.max(self._machineSize) * 3
652 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
653 self._mouseState = 'dragObject'
654 z = max(0, self._mouseClick3DPos[2])
655 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
656 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
661 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
662 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
663 diff = cursorZ1 - cursorZ0
664 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
665 if not e.Dragging() or self._mouseState != 'tool':
666 self.tool.OnMouseMove(p0, p1)
668 self._mouseX = e.GetX()
669 self._mouseY = e.GetY()
671 def OnMouseWheel(self, e):
672 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
673 delta = max(min(delta,4),-4)
674 self._zoom *= 1.0 - delta / 10.0
677 if self._zoom > numpy.max(self._machineSize) * 3:
678 self._zoom = numpy.max(self._machineSize) * 3
681 def getMouseRay(self, x, y):
682 if self._viewport is None:
683 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
684 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
685 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
686 p0 -= self._viewTarget
687 p1 -= self._viewTarget
690 def _init3DView(self):
691 # set viewing projection
692 size = self.GetSize()
693 glViewport(0, 0, size.GetWidth(), size.GetHeight())
696 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
698 glDisable(GL_RESCALE_NORMAL)
699 glDisable(GL_LIGHTING)
701 glEnable(GL_DEPTH_TEST)
702 glDisable(GL_CULL_FACE)
704 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
706 glClearColor(0.8, 0.8, 0.8, 1.0)
710 glMatrixMode(GL_PROJECTION)
712 aspect = float(size.GetWidth()) / float(size.GetHeight())
713 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
715 glMatrixMode(GL_MODELVIEW)
717 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
720 if machineCom.machineIsConnected():
721 self.printButton._imageID = 6
722 self.printButton._tooltip = 'Print'
723 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
724 self.printButton._imageID = 2
725 self.printButton._tooltip = 'Toolpath to SD'
727 self.printButton._imageID = 3
728 self.printButton._tooltip = 'Save toolpath'
730 if self._animView is not None:
731 self._viewTarget = self._animView.getPosition()
732 if self._animView.isDone():
733 self._animView = None
734 if self._animZoom is not None:
735 self._zoom = self._animZoom.getPosition()
736 if self._animZoom.isDone():
737 self._animZoom = None
738 if self.viewMode == 'gcode' and self._gcode is not None:
740 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
743 if self._objectShader is None:
744 self._objectShader = opengl.GLShader("""
745 varying float light_amount;
749 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
750 gl_FrontColor = gl_Color;
752 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
756 varying float light_amount;
760 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
763 self._objectOverhangShader = opengl.GLShader("""
764 uniform float cosAngle;
765 uniform mat3 rotMatrix;
766 varying float light_amount;
770 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
771 gl_FrontColor = gl_Color;
773 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
775 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
781 varying float light_amount;
785 if (light_amount == -10)
787 gl_FragColor = vec4(1, 0, 0, gl_Color[3]);
789 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
793 self._objectLoadShader = opengl.GLShader("""
794 uniform float intensity;
796 varying float light_amount;
800 vec4 tmp = gl_Vertex;
801 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
802 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
803 gl_Position = gl_ModelViewProjectionMatrix * tmp;
804 gl_FrontColor = gl_Color;
806 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
810 uniform float intensity;
811 varying float light_amount;
815 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
819 glTranslate(0,0,-self._zoom)
820 glRotate(-self._pitch, 1,0,0)
821 glRotate(self._yaw, 0,0,1)
822 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
824 self._viewport = glGetIntegerv(GL_VIEWPORT)
825 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
826 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
828 glClearColor(1,1,1,1)
829 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
831 if self.viewMode != 'gcode':
832 for n in xrange(0, len(self._scene.objects())):
833 obj = self._scene.objects()[n]
834 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
835 self._renderObject(obj)
837 if self._mouseX > -1:
839 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
840 if n < len(self._scene.objects()):
841 self._focusObj = self._scene.objects()[n]
843 self._focusObj = None
844 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
845 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
846 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
847 self._mouse3Dpos -= self._viewTarget
850 glTranslate(0,0,-self._zoom)
851 glRotate(-self._pitch, 1,0,0)
852 glRotate(self._yaw, 0,0,1)
853 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
855 if self.viewMode == 'gcode':
856 if self._gcode is not None:
858 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
860 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
861 for n in xrange(0, drawUpTill):
862 c = 1.0 - float(drawUpTill - n) / 15
864 if len(self._gcodeVBOs) < n + 1:
865 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
866 if time.time() - t > 0.5:
869 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
870 if n == drawUpTill - 1:
871 if len(self._gcodeVBOs[n]) < 6:
872 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
874 self._gcodeVBOs[n][5].render(GL_QUADS)
876 self._gcodeVBOs[n][6].render(GL_QUADS)
877 glColor3f(c/2, c/2, 0.0)
878 self._gcodeVBOs[n][7].render(GL_QUADS)
880 self._gcodeVBOs[n][8].render(GL_QUADS)
881 self._gcodeVBOs[n][9].render(GL_QUADS)
883 self._gcodeVBOs[n][10].render(GL_LINES)
886 self._gcodeVBOs[n][0].render(GL_LINES)
888 self._gcodeVBOs[n][1].render(GL_LINES)
889 glColor3f(c/2, c/2, 0.0)
890 self._gcodeVBOs[n][2].render(GL_LINES)
892 self._gcodeVBOs[n][3].render(GL_LINES)
893 self._gcodeVBOs[n][4].render(GL_LINES)
896 glStencilFunc(GL_ALWAYS, 1, 1)
897 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
899 if self.viewMode == 'overhang':
900 self._objectOverhangShader.bind()
901 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(60)))
903 self._objectShader.bind()
904 for obj in self._scene.objects():
905 if obj._loadAnim is not None:
906 if obj._loadAnim.isDone():
911 if self._focusObj == obj:
913 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
916 if self._selectedObj == obj or self._selectedObj is None:
917 #If we want transparent, then first render a solid black model to remove the printer size lines.
918 if self.viewMode == 'transparent':
919 glColor4f(0, 0, 0, 0)
920 self._renderObject(obj)
922 glBlendFunc(GL_ONE, GL_ONE)
923 glDisable(GL_DEPTH_TEST)
925 if self.viewMode == 'xray':
926 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
927 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
928 glEnable(GL_STENCIL_TEST)
930 if self.viewMode == 'overhang':
931 if self._selectedObj == obj and self.tempMatrix is not None:
932 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
934 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
936 if not self._scene.checkPlatform(obj):
937 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
938 self._renderObject(obj)
940 self._renderObject(obj, brightness)
941 glDisable(GL_STENCIL_TEST)
943 glEnable(GL_DEPTH_TEST)
944 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
946 if self.viewMode == 'xray':
949 glEnable(GL_STENCIL_TEST)
950 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
951 glDisable(GL_DEPTH_TEST)
952 for i in xrange(2, 15, 2):
953 glStencilFunc(GL_EQUAL, i, 0xFF)
954 glColor(float(i)/10, float(i)/10, float(i)/5)
956 glVertex3f(-1000,-1000,-10)
957 glVertex3f( 1000,-1000,-10)
958 glVertex3f( 1000, 1000,-10)
959 glVertex3f(-1000, 1000,-10)
961 for i in xrange(1, 15, 2):
962 glStencilFunc(GL_EQUAL, i, 0xFF)
963 glColor(float(i)/10, 0, 0)
965 glVertex3f(-1000,-1000,-10)
966 glVertex3f( 1000,-1000,-10)
967 glVertex3f( 1000, 1000,-10)
968 glVertex3f(-1000, 1000,-10)
971 glDisable(GL_STENCIL_TEST)
972 glEnable(GL_DEPTH_TEST)
974 self._objectShader.unbind()
976 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
978 self._objectLoadShader.bind()
979 glColor4f(0.2, 0.6, 1.0, 1.0)
980 for obj in self._scene.objects():
981 if obj._loadAnim is None:
983 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
984 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
985 self._renderObject(obj)
986 self._objectLoadShader.unbind()
991 if self.viewMode == 'gcode':
994 #Draw the object box-shadow, so you can see where it will collide with other objects.
995 if self._selectedObj is not None and len(self._scene.objects()) > 1:
996 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
998 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1000 glEnable(GL_CULL_FACE)
1001 glColor4f(0,0,0,0.12)
1003 glVertex3f(-size[0], size[1], 0.1)
1004 glVertex3f(-size[0], -size[1], 0.1)
1005 glVertex3f( size[0], -size[1], 0.1)
1006 glVertex3f( size[0], size[1], 0.1)
1008 glDisable(GL_CULL_FACE)
1011 #Draw the outline of the selected object, on top of everything else except the GUI.
1012 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1013 glDisable(GL_DEPTH_TEST)
1014 glEnable(GL_CULL_FACE)
1015 glEnable(GL_STENCIL_TEST)
1017 glStencilFunc(GL_EQUAL, 0, 255)
1019 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1021 glColor4f(1,1,1,0.5)
1022 self._renderObject(self._selectedObj)
1023 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1025 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1026 glDisable(GL_STENCIL_TEST)
1027 glDisable(GL_CULL_FACE)
1028 glEnable(GL_DEPTH_TEST)
1030 if self._selectedObj is not None:
1032 pos = self.getObjectCenterPos()
1033 glTranslate(pos[0], pos[1], pos[2])
1037 def _renderObject(self, obj, brightness = False, addSink = True):
1040 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1042 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1044 if self.tempMatrix is not None and obj == self._selectedObj:
1045 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1046 glMultMatrixf(tempMatrix)
1048 offset = obj.getDrawOffset()
1049 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1051 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1052 glMultMatrixf(tempMatrix)
1055 for m in obj._meshList:
1057 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1059 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1064 def _drawMachine(self):
1065 glEnable(GL_CULL_FACE)
1068 if profile.getPreference('machine_type') == 'ultimaker':
1069 glColor4f(1,1,1,0.5)
1070 self._objectShader.bind()
1071 self._renderObject(self._platformMesh, False, False)
1072 self._objectShader.unbind()
1074 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1075 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1076 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1077 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1078 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1079 v4 = [ size[0] / 2, size[1] / 2, 0]
1080 v5 = [ size[0] / 2,-size[1] / 2, 0]
1081 v6 = [-size[0] / 2, size[1] / 2, 0]
1082 v7 = [-size[0] / 2,-size[1] / 2, 0]
1084 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1085 glEnableClientState(GL_VERTEX_ARRAY)
1086 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1088 glColor4ub(5, 171, 231, 64)
1089 glDrawArrays(GL_QUADS, 0, 4)
1090 glColor4ub(5, 171, 231, 96)
1091 glDrawArrays(GL_QUADS, 4, 8)
1092 glColor4ub(5, 171, 231, 128)
1093 glDrawArrays(GL_QUADS, 12, 8)
1095 sx = self._machineSize[0]
1096 sy = self._machineSize[1]
1097 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1098 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1103 x1 = max(min(x1, sx/2), -sx/2)
1104 y1 = max(min(y1, sy/2), -sy/2)
1105 x2 = max(min(x2, sx/2), -sx/2)
1106 y2 = max(min(y2, sy/2), -sy/2)
1107 if (x & 1) == (y & 1):
1108 glColor4ub(5, 171, 231, 127)
1110 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1112 glVertex3f(x1, y1, -0.02)
1113 glVertex3f(x2, y1, -0.02)
1114 glVertex3f(x2, y2, -0.02)
1115 glVertex3f(x1, y2, -0.02)
1118 glDisableClientState(GL_VERTEX_ARRAY)
1120 glDisable(GL_CULL_FACE)
1122 def _generateGCodeVBOs(self, layer):
1124 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1125 pointList = numpy.zeros((0,3), numpy.float32)
1127 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1129 a = numpy.concatenate((a[:-1], a[1:]), 1)
1130 a = a.reshape((len(a) * 2, 3))
1131 pointList = numpy.concatenate((pointList, a))
1132 ret.append(opengl.GLVBO(pointList))
1135 def _generateGCodeVBOs2(self, layer):
1136 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1137 filamentArea = math.pi * filamentRadius * filamentRadius
1140 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1141 pointList = numpy.zeros((0,3), numpy.float32)
1143 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1145 if extrudeType == 'FILL':
1148 normal = a[1:] - a[:-1]
1149 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1150 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1153 ePerDist = path['extrusion'][1:] / lens
1154 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1156 normal[:,0] *= lineWidth
1157 normal[:,1] *= lineWidth
1159 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1160 b = numpy.concatenate((b, a[1:] + normal), 1)
1161 b = numpy.concatenate((b, a[1:] - normal), 1)
1162 b = numpy.concatenate((b, a[:-1] - normal), 1)
1163 b = numpy.concatenate((b, a[:-1] + normal), 1)
1164 #b = numpy.concatenate((b, a[:-1]), 1)
1165 #b = numpy.concatenate((b, a[:-1]), 1)
1166 b = b.reshape((len(b) * 4, 3))
1168 pointList = numpy.concatenate((pointList, b))
1169 ret.append(opengl.GLVBO(pointList))
1171 pointList = numpy.zeros((0,3), numpy.float32)
1173 if path['type'] == 'move' or path['type'] == 'retract':
1175 a = numpy.concatenate((a[:-1], a[1:]), 1)
1176 a = a.reshape((len(a) * 2, 3))
1177 pointList = numpy.concatenate((pointList, a))
1178 ret.append(opengl.GLVBO(pointList))
1182 def getObjectCenterPos(self):
1183 if self._selectedObj is None:
1184 return [0.0, 0.0, 0.0]
1185 pos = self._selectedObj.getPosition()
1186 size = self._selectedObj.getSize()
1187 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1189 def getObjectBoundaryCircle(self):
1190 if self._selectedObj is None:
1192 return self._selectedObj.getBoundaryCircle()
1194 def getObjectSize(self):
1195 if self._selectedObj is None:
1196 return [0.0, 0.0, 0.0]
1197 return self._selectedObj.getSize()
1199 def getObjectMatrix(self):
1200 if self._selectedObj is None:
1201 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1202 return self._selectedObj.getMatrix()
1204 class shaderEditor(wx.Dialog):
1205 def __init__(self, parent, callback, v, f):
1206 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1207 self._callback = callback
1208 s = wx.BoxSizer(wx.VERTICAL)
1210 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1211 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1212 s.Add(self._vertex, 1, flag=wx.EXPAND)
1213 s.Add(self._fragment, 1, flag=wx.EXPAND)
1215 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1216 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1218 self.SetPosition(self.GetParent().GetPosition())
1219 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1222 def OnText(self, e):
1223 self._callback(self._vertex.GetValue(), self._fragment.GetValue())