-from __future__ import division\r
-\r
-import sys, math, threading, re, time, os\r
-import numpy\r
-\r
-from wx import glcanvas\r
-import wx\r
-try:\r
- import OpenGL\r
- OpenGL.ERROR_CHECKING = False\r
- from OpenGL.GLU import *\r
- from OpenGL.GL import *\r
- hasOpenGLlibs = True\r
-except:\r
- print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
- hasOpenGLlibs = False\r
-\r
-from gui import opengl\r
-from gui import toolbarUtil\r
-\r
-from util import profile\r
-from util import gcodeInterpreter\r
-from util import meshLoader\r
-from util import util3d\r
-from util import sliceRun\r
-\r
-class previewObject():\r
- def __init__(self):\r
- self.mesh = None\r
- self.filename = None\r
- self.displayList = None\r
- self.dirty = False\r
-\r
-class previewPanel(wx.Panel):\r
- def __init__(self, parent):\r
- super(previewPanel, self).__init__(parent,-1)\r
- \r
- self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))\r
- self.SetMinSize((440,320))\r
- \r
- self.objectList = []\r
- self.errorList = []\r
- self.gcode = None\r
- self.objectsMinV = None\r
- self.objectsMaxV = None\r
- self.objectsBounderyCircleSize = None\r
- self.loadThread = None\r
- self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))\r
- self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)\r
-\r
- self.glCanvas = PreviewGLCanvas(self)\r
- #Create the popup window\r
- self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)\r
- self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))\r
- self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')\r
- self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)\r
- self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)\r
- self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)\r
- self.warningPopup.SetSizer(self.warningPopup.sizer)\r
- self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)\r
- self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)\r
- self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)\r
- self.warningPopup.Fit()\r
- self.warningPopup.Layout()\r
- self.warningPopup.timer = wx.Timer(self)\r
- self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)\r
- \r
- self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)\r
- self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)\r
- parent.Bind(wx.EVT_MOVE, self.OnMove)\r
- parent.Bind(wx.EVT_SIZE, self.OnMove)\r
- \r
- self.toolbar = toolbarUtil.Toolbar(self)\r
-\r
- group = []\r
- toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)\r
- toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)\r
- self.toolbar.AddSeparator()\r
-\r
- self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)\r
- self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)\r
- self.toolbar.AddSeparator()\r
-\r
- group = []\r
- self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)\r
- self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)\r
- self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)\r
- self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)\r
- self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)\r
- self.toolbar.AddSeparator()\r
-\r
- self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
- self.toolbar.AddControl(self.layerSpin)\r
- self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)\r
- self.toolbar.AddSeparator()\r
- self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)\r
- self.toolbar.AddControl(self.toolbarInfo)\r
-\r
- self.toolbar2 = toolbarUtil.Toolbar(self)\r
-\r
- # Mirror\r
- self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)\r
- self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)\r
- self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)\r
- self.toolbar2.AddSeparator()\r
-\r
- # Swap\r
- self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)\r
- self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)\r
- self.toolbar2.AddSeparator()\r
-\r
- # Scale\r
- self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')\r
- self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))\r
- self.toolbar2.AddControl(self.scale)\r
- self.scale.Bind(wx.EVT_TEXT, self.OnScale)\r
- self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')\r
-\r
- self.toolbar2.AddSeparator()\r
-\r
- # Multiply\r
- #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')\r
- #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')\r
- #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')\r
- #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')\r
- #self.toolbar2.AddSeparator()\r
-\r
- # Rotate\r
- self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')\r
- self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)\r
- self.rotate.SetRange(0, 360)\r
- self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)\r
- self.toolbar2.AddControl(self.rotate)\r
-\r
- self.toolbar2.Realize()\r
- self.OnViewChange()\r
- \r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
- sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)\r
- sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)\r
- sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)\r
- self.SetSizer(sizer)\r
- \r
- def returnToModelViewAndUpdateModel(self):\r
- if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':\r
- self.setViewMode('Normal')\r
- self.updateModelTransform()\r
- \r
- def OnMove(self, e = None):\r
- if e != None:\r
- e.Skip()\r
- x, y = self.glCanvas.ClientToScreenXY(0, 0)\r
- sx, sy = self.glCanvas.GetClientSizeTuple()\r
- self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))\r
- \r
- def OnMulXAddClick(self, e):\r
- profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))\r
- self.glCanvas.Refresh()\r
-\r
- def OnMulXSubClick(self, e):\r
- profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))\r
- self.glCanvas.Refresh()\r
-\r
- def OnMulYAddClick(self, e):\r
- profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))\r
- self.glCanvas.Refresh()\r
-\r
- def OnMulYSubClick(self, e):\r
- profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))\r
- self.glCanvas.Refresh()\r
-\r
- def OnScaleReset(self, e):\r
- self.scale.SetValue('1.0')\r
- self.OnScale(None)\r
-\r
- def OnScale(self, e):\r
- scale = 1.0\r
- if self.scale.GetValue() != '':\r
- scale = self.scale.GetValue()\r
- profile.putProfileSetting('model_scale', scale)\r
- if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':\r
- self.setViewMode('Normal')\r
- self.glCanvas.Refresh()\r
-\r
- if self.objectsMaxV != None:\r
- size = (self.objectsMaxV - self.objectsMinV) * float(scale)\r
- self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))\r
-\r
- def OnScaleMax(self, e = None, onlyScaleDown = False):\r
- if self.objectsMinV == None:\r
- return\r
- vMin = self.objectsMinV\r
- vMax = self.objectsMaxV\r
- skirtSize = 3\r
- if profile.getProfileSettingFloat('skirt_line_count') > 0:\r
- skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')\r
- scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)\r
- scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)\r
- scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)\r
- scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)\r
- scaleZ = self.machineSize.z / (vMax[2] - vMin[2])\r
- scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)\r
- if scale > 1.0 and onlyScaleDown:\r
- return\r
- self.scale.SetValue(str(scale))\r
- profile.putProfileSetting('model_scale', self.scale.GetValue())\r
- if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':\r
- self.setViewMode('Normal')\r
- self.glCanvas.Refresh()\r
-\r
- def OnRotateReset(self, e):\r
- self.rotate.SetValue(0)\r
- self.OnRotate(None)\r
-\r
- def OnRotate(self, e):\r
- profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())\r
- self.returnToModelViewAndUpdateModel()\r
-\r
- def On3DClick(self):\r
- self.glCanvas.yaw = 30\r
- self.glCanvas.pitch = 60\r
- self.glCanvas.zoom = 300\r
- self.glCanvas.view3D = True\r
- self.glCanvas.Refresh()\r
-\r
- def OnTopClick(self):\r
- self.glCanvas.view3D = False\r
- self.glCanvas.zoom = 100\r
- self.glCanvas.offsetX = 0\r
- self.glCanvas.offsetY = 0\r
- self.glCanvas.Refresh()\r
-\r
- def OnLayerNrChange(self, e):\r
- self.glCanvas.Refresh()\r
- \r
- def setViewMode(self, mode):\r
- if mode == "Normal":\r
- self.normalViewButton.SetValue(True)\r
- if mode == "GCode":\r
- self.gcodeViewButton.SetValue(True)\r
- self.glCanvas.viewMode = mode\r
- wx.CallAfter(self.glCanvas.Refresh)\r
- \r
- def loadModelFiles(self, filelist, showWarning = False):\r
- while len(filelist) > len(self.objectList):\r
- self.objectList.append(previewObject())\r
- for idx in xrange(len(filelist), len(self.objectList)):\r
- self.objectList[idx].mesh = None\r
- self.objectList[idx].filename = None\r
- for idx in xrange(0, len(filelist)):\r
- obj = self.objectList[idx]\r
- if obj.filename != filelist[idx]:\r
- obj.fileTime = None\r
- self.gcodeFileTime = None\r
- self.logFileTime = None\r
- obj.filename = filelist[idx]\r
- \r
- self.gcodeFilename = sliceRun.getExportFilename(filelist[0])\r
- #Do the STL file loading in a background thread so we don't block the UI.\r
- if self.loadThread != None and self.loadThread.isAlive():\r
- self.loadThread.join()\r
- self.loadThread = threading.Thread(target=self.doFileLoadThread)\r
- self.loadThread.daemon = True\r
- self.loadThread.start()\r
- \r
- if showWarning:\r
- if profile.getProfileSettingFloat('model_scale') != 1.0 or profile.getProfileSettingFloat('model_rotate_base') != 0 or profile.getProfileSetting('flip_x') != 'False' or profile.getProfileSetting('flip_y') != 'False' or profile.getProfileSetting('flip_z') != 'False' or profile.getProfileSetting('swap_xz') != 'False' or profile.getProfileSetting('swap_yz') != 'False' or len(profile.getPluginConfig()) > 0:\r
- self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)\r
- \r
- def loadReModelFiles(self, filelist):\r
- #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)\r
- for idx in xrange(0, len(filelist)):\r
- if self.objectList[idx].filename != filelist[idx]:\r
- return False\r
- self.loadModelFiles(filelist)\r
- return True\r
- \r
- def doFileLoadThread(self):\r
- for obj in self.objectList:\r
- if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:\r
- obj.ileTime = os.stat(obj.filename).st_mtime\r
- mesh = meshLoader.loadMesh(obj.filename)\r
- obj.dirty = False\r
- obj.mesh = mesh\r
- self.updateModelTransform()\r
- self.OnScaleMax(None, True)\r
- scale = profile.getProfileSettingFloat('model_scale')\r
- size = (self.objectsMaxV - self.objectsMinV) * scale\r
- self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))\r
- self.glCanvas.zoom = numpy.max(size) * 2.5\r
- self.errorList = []\r
- wx.CallAfter(self.updateToolbar)\r
- wx.CallAfter(self.glCanvas.Refresh)\r
- \r
- if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:\r
- self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime\r
- gcode = gcodeInterpreter.gcode()\r
- gcode.progressCallback = self.loadProgress\r
- gcode.load(self.gcodeFilename)\r
- self.gcodeDirty = False\r
- self.gcode = gcode\r
- self.gcodeDirty = True\r
-\r
- errorList = []\r
- for line in open(self.gcodeFilename, "rt"):\r
- res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)\r
- if res != None:\r
- v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))\r
- v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))\r
- errorList.append([v1, v2])\r
- self.errorList = errorList\r
-\r
- wx.CallAfter(self.updateToolbar)\r
- wx.CallAfter(self.glCanvas.Refresh)\r
- elif not os.path.isfile(self.gcodeFilename):\r
- self.gcode = None\r
- \r
- def loadProgress(self, progress):\r
- pass\r
-\r
- def OnResetAll(self, e = None):\r
- profile.putProfileSetting('model_scale', '1.0')\r
- profile.putProfileSetting('model_rotate_base', '0')\r
- profile.putProfileSetting('flip_x', 'False')\r
- profile.putProfileSetting('flip_y', 'False')\r
- profile.putProfileSetting('flip_z', 'False')\r
- profile.putProfileSetting('swap_xz', 'False')\r
- profile.putProfileSetting('swap_yz', 'False')\r
- profile.setPluginConfig([])\r
- self.GetParent().updateProfileToControls()\r
-\r
- def ShowWarningPopup(self, text, callback = None):\r
- self.warningPopup.text.SetLabel(text)\r
- self.warningPopup.callback = callback\r
- if callback == None:\r
- self.warningPopup.yesButton.Show(False)\r
- self.warningPopup.noButton.SetLabel('ok')\r
- else:\r
- self.warningPopup.yesButton.Show(True)\r
- self.warningPopup.noButton.SetLabel('no')\r
- self.warningPopup.Fit()\r
- self.warningPopup.Layout()\r
- self.OnMove()\r
- self.warningPopup.Show(True)\r
- self.warningPopup.timer.Start(5000)\r
- \r
- def OnWarningPopup(self, e):\r
- self.warningPopup.Show(False)\r
- self.warningPopup.timer.Stop()\r
- self.warningPopup.callback()\r
-\r
- def OnHideWarning(self, e):\r
- self.warningPopup.Show(False)\r
- self.warningPopup.timer.Stop()\r
-\r
- def updateToolbar(self):\r
- self.gcodeViewButton.Show(self.gcode != None)\r
- self.mixedViewButton.Show(self.gcode != None)\r
- self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")\r
- if self.gcode != None:\r
- self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)\r
- self.toolbar.Realize()\r
- self.Update()\r
- \r
- def OnViewChange(self):\r
- if self.normalViewButton.GetValue():\r
- self.glCanvas.viewMode = "Normal"\r
- elif self.transparentViewButton.GetValue():\r
- self.glCanvas.viewMode = "Transparent"\r
- elif self.xrayViewButton.GetValue():\r
- self.glCanvas.viewMode = "X-Ray"\r
- elif self.gcodeViewButton.GetValue():\r
- self.glCanvas.viewMode = "GCode"\r
- elif self.mixedViewButton.GetValue():\r
- self.glCanvas.viewMode = "Mixed"\r
- self.glCanvas.drawBorders = self.showBorderButton.GetValue()\r
- self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()\r
- self.updateToolbar()\r
- self.glCanvas.Refresh()\r
- \r
- def updateModelTransform(self, f=0):\r
- if len(self.objectList) < 1 or self.objectList[0].mesh == None:\r
- return\r
- \r
- rotate = profile.getProfileSettingFloat('model_rotate_base')\r
- mirrorX = profile.getProfileSetting('flip_x') == 'True'\r
- mirrorY = profile.getProfileSetting('flip_y') == 'True'\r
- mirrorZ = profile.getProfileSetting('flip_z') == 'True'\r
- swapXZ = profile.getProfileSetting('swap_xz') == 'True'\r
- swapYZ = profile.getProfileSetting('swap_yz') == 'True'\r
-\r
- for obj in self.objectList:\r
- if obj.mesh == None:\r
- continue\r
- obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)\r
- \r
- minV = self.objectList[0].mesh.getMinimum()\r
- maxV = self.objectList[0].mesh.getMaximum()\r
- objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize\r
- for obj in self.objectList:\r
- if obj.mesh == None:\r
- continue\r
-\r
- obj.mesh.getMinimumZ()\r
- minV = numpy.minimum(minV, obj.mesh.getMinimum())\r
- maxV = numpy.maximum(maxV, obj.mesh.getMaximum())\r
- objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)\r
-\r
- self.objectsMaxV = maxV\r
- self.objectsMinV = minV\r
- self.objectsBounderyCircleSize = objectsBounderyCircleSize\r
- for obj in self.objectList:\r
- if obj.mesh == None:\r
- continue\r
-\r
- obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])\r
- #for v in obj.mesh.vertexes:\r
- # v[2] -= minV[2]\r
- # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2\r
- # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2\r
- obj.mesh.getMinimumZ()\r
- obj.dirty = True\r
-\r
- scale = profile.getProfileSettingFloat('model_scale')\r
- size = (self.objectsMaxV - self.objectsMinV) * scale\r
- self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))\r
-\r
- self.glCanvas.Refresh()\r
- \r
- def updateProfileToControls(self):\r
- self.scale.SetValue(profile.getProfileSetting('model_scale'))\r
- self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))\r
- self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')\r
- self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')\r
- self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')\r
- self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')\r
- self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')\r
- self.updateModelTransform()\r
- self.glCanvas.updateProfileToControls()\r
-\r
-class PreviewGLCanvas(glcanvas.GLCanvas):\r
- def __init__(self, parent):\r
- attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
- glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
- self.parent = parent\r
- self.context = glcanvas.GLContext(self)\r
- wx.EVT_PAINT(self, self.OnPaint)\r
- wx.EVT_SIZE(self, self.OnSize)\r
- wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
- wx.EVT_MOTION(self, self.OnMouseMotion)\r
- wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
- self.yaw = 30\r
- self.pitch = 60\r
- self.zoom = 300\r
- self.offsetX = 0\r
- self.offsetY = 0\r
- self.view3D = True\r
- self.gcodeDisplayList = None\r
- self.gcodeDisplayListMade = None\r
- self.gcodeDisplayListCount = 0\r
- self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]\r
- self.oldX = 0\r
- self.oldY = 0\r
- self.dragType = ''\r
- self.tempRotate = 0\r
- \r
- def updateProfileToControls(self):\r
- self.objColor[0] = profile.getPreferenceColour('model_colour')\r
- self.objColor[1] = profile.getPreferenceColour('model_colour2')\r
- self.objColor[2] = profile.getPreferenceColour('model_colour3')\r
- self.objColor[3] = profile.getPreferenceColour('model_colour4')\r
-\r
- def OnMouseMotion(self,e):\r
- cursorXY = 100000\r
- radius = 0\r
- if self.parent.objectsMaxV != None:\r
- radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')\r
- \r
- p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))\r
- p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))\r
- cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))\r
- cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))\r
- if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:\r
- self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))\r
- else:\r
- self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))\r
-\r
- if e.Dragging() and e.LeftIsDown():\r
- if self.dragType == '':\r
- #Define the drag type depending on the cursor position.\r
- if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:\r
- self.dragType = 'modelRotate'\r
- self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])\r
- else:\r
- self.dragType = 'viewRotate'\r
- \r
- if self.dragType == 'viewRotate':\r
- if self.view3D:\r
- self.yaw += e.GetX() - self.oldX\r
- self.pitch -= e.GetY() - self.oldY\r
- if self.pitch > 170:\r
- self.pitch = 170\r
- if self.pitch < 10:\r
- self.pitch = 10\r
- else:\r
- self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
- self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
- elif self.dragType == 'modelRotate':\r
- angle = math.atan2(cursorZ0[0], cursorZ0[1])\r
- diff = self.dragStart - angle\r
- self.tempRotate = diff * 180 / math.pi\r
- rot = profile.getProfileSettingFloat('model_rotate_base')\r
- self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot\r
- #Workaround for buggy ATI cards.\r
- size = self.GetSizeTuple()\r
- self.SetSize((size[0]+1, size[1]))\r
- self.SetSize((size[0], size[1]))\r
- self.Refresh()\r
- else:\r
- if self.tempRotate != 0:\r
- newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate\r
- while newRotation >= 360:\r
- newRotation -= 360\r
- while newRotation < 0:\r
- newRotation += 360\r
- profile.putProfileSetting('model_rotate_base', newRotation)\r
- self.parent.rotate.SetValue(newRotation)\r
- self.parent.updateModelTransform()\r
- self.tempRotate = 0\r
- \r
- self.dragType = ''\r
- if e.Dragging() and e.RightIsDown():\r
- self.zoom += e.GetY() - self.oldY\r
- if self.zoom < 1:\r
- self.zoom = 1\r
- if self.zoom > 500:\r
- self.zoom = 500\r
- self.Refresh()\r
- self.oldX = e.GetX()\r
- self.oldY = e.GetY()\r
-\r
- #self.Refresh()\r
- \r
- \r
- def OnMouseWheel(self,e):\r
- self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
- if self.zoom < 1.0:\r
- self.zoom = 1.0\r
- if self.zoom > 500:\r
- self.zoom = 500\r
- self.Refresh()\r
- \r
- def OnEraseBackground(self,event):\r
- #Workaround for windows background redraw flicker.\r
- pass\r
- \r
- def OnSize(self,e):\r
- self.Refresh()\r
-\r
- def OnPaint(self,e):\r
- dc = wx.PaintDC(self)\r
- if not hasOpenGLlibs:\r
- dc.Clear()\r
- dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
- return\r
- self.SetCurrent(self.context)\r
- opengl.InitGL(self, self.view3D, self.zoom)\r
- if self.view3D:\r
- glTranslate(0,0,-self.zoom)\r
- glRotate(-self.pitch, 1,0,0)\r
- glRotate(self.yaw, 0,0,1)\r
- if self.viewMode == "GCode" or self.viewMode == "Mixed":\r
- if self.parent.gcode != None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:\r
- glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)\r
- else:\r
- if self.parent.objectsMaxV != None:\r
- glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)\r
- else:\r
- glTranslate(self.offsetX, self.offsetY, 0)\r
-\r
- self.viewport = glGetIntegerv(GL_VIEWPORT);\r
- self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);\r
- self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);\r
-\r
- glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)\r
-\r
- self.OnDraw()\r
- self.SwapBuffers()\r
-\r
- def OnDraw(self):\r
- machineSize = self.parent.machineSize\r
-\r
- if self.parent.gcode != None and self.parent.gcodeDirty:\r
- if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:\r
- if self.gcodeDisplayList != None:\r
- glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)\r
- self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));\r
- self.gcodeDisplayListCount = len(self.parent.gcode.layerList)\r
- self.parent.gcodeDirty = False\r
- self.gcodeDisplayListMade = 0\r
- \r
- if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):\r
- glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)\r
- opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])\r
- glEndList()\r
- self.gcodeDisplayListMade += 1\r
- wx.CallAfter(self.Refresh)\r
- \r
- glPushMatrix()\r
- glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
- for obj in self.parent.objectList:\r
- if obj.mesh == None:\r
- continue\r
- if obj.displayList == None:\r
- obj.displayList = glGenLists(1)\r
- obj.steepDisplayList = glGenLists(1)\r
- if obj.dirty:\r
- obj.dirty = False\r
- glNewList(obj.displayList, GL_COMPILE)\r
- opengl.DrawMesh(obj.mesh)\r
- glEndList()\r
- glNewList(obj.steepDisplayList, GL_COMPILE)\r
- opengl.DrawMeshSteep(obj.mesh, 60)\r
- glEndList()\r
- \r
- if self.viewMode == "Mixed":\r
- glDisable(GL_BLEND)\r
- glColor3f(0.0,0.0,0.0)\r
- self.drawModel(obj)\r
- glColor3f(1.0,1.0,1.0)\r
- glClear(GL_DEPTH_BUFFER_BIT)\r
- \r
- glPopMatrix()\r
- \r
- if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):\r
- glEnable(GL_COLOR_MATERIAL)\r
- glEnable(GL_LIGHTING)\r
- drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)\r
- starttime = time.time()\r
- for i in xrange(drawUpToLayer - 1, -1, -1):\r
- c = 1.0\r
- if i < self.parent.layerSpin.GetValue():\r
- c = 0.9 - (drawUpToLayer - i) * 0.1\r
- if c < 0.4:\r
- c = (0.4 + c) / 2\r
- if c < 0.1:\r
- c = 0.1\r
- glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])\r
- glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])\r
- glCallList(self.gcodeDisplayList + i)\r
- if time.time() - starttime > 0.1:\r
- break\r
-\r
- glDisable(GL_LIGHTING)\r
- glDisable(GL_COLOR_MATERIAL)\r
- glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);\r
- glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);\r
-\r
- glColor3f(1.0,1.0,1.0)\r
- glPushMatrix()\r
- glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
- for obj in self.parent.objectList:\r
- if obj.mesh == None:\r
- continue\r
- \r
- if self.viewMode == "Transparent" or self.viewMode == "Mixed":\r
- glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))\r
- glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))\r
- #If we want transparent, then first render a solid black model to remove the printer size lines.\r
- if self.viewMode != "Mixed":\r
- glDisable(GL_BLEND)\r
- glColor3f(0.0,0.0,0.0)\r
- self.drawModel(obj)\r
- glColor3f(1.0,1.0,1.0)\r
- #After the black model is rendered, render the model again but now with lighting and no depth testing.\r
- glDisable(GL_DEPTH_TEST)\r
- glEnable(GL_LIGHTING)\r
- glEnable(GL_BLEND)\r
- glBlendFunc(GL_ONE, GL_ONE)\r
- glEnable(GL_LIGHTING)\r
- self.drawModel(obj)\r
- glEnable(GL_DEPTH_TEST)\r
- elif self.viewMode == "X-Ray":\r
- glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
- glDisable(GL_LIGHTING)\r
- glDisable(GL_DEPTH_TEST)\r
- glEnable(GL_STENCIL_TEST)\r
- glStencilFunc(GL_ALWAYS, 1, 1)\r
- glStencilOp(GL_INCR, GL_INCR, GL_INCR)\r
- self.drawModel(obj)\r
- glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);\r
- \r
- glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
- glStencilFunc(GL_EQUAL, 0, 1)\r
- glColor(1, 1, 1)\r
- self.drawModel(obj)\r
- glStencilFunc(GL_EQUAL, 1, 1)\r
- glColor(1, 0, 0)\r
- self.drawModel(obj)\r
-\r
- glPushMatrix()\r
- glLoadIdentity()\r
- for i in xrange(2, 15, 2):\r
- glStencilFunc(GL_EQUAL, i, 0xFF);\r
- glColor(float(i)/10, float(i)/10, float(i)/5)\r
- glBegin(GL_QUADS)\r
- glVertex3f(-1000,-1000,-1)\r
- glVertex3f( 1000,-1000,-1)\r
- glVertex3f( 1000, 1000,-1)\r
- glVertex3f(-1000, 1000,-1)\r
- glEnd()\r
- for i in xrange(1, 15, 2):\r
- glStencilFunc(GL_EQUAL, i, 0xFF);\r
- glColor(float(i)/10, 0, 0)\r
- glBegin(GL_QUADS)\r
- glVertex3f(-1000,-1000,-1)\r
- glVertex3f( 1000,-1000,-1)\r
- glVertex3f( 1000, 1000,-1)\r
- glVertex3f(-1000, 1000,-1)\r
- glEnd()\r
- glPopMatrix()\r
-\r
- glDisable(GL_STENCIL_TEST)\r
- glEnable(GL_DEPTH_TEST)\r
- \r
- #Fix the depth buffer\r
- glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
- self.drawModel(obj)\r
- glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
- elif self.viewMode == "Normal":\r
- glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])\r
- glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))\r
- glEnable(GL_LIGHTING)\r
- self.drawModel(obj)\r
-\r
- if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):\r
- glEnable(GL_DEPTH_TEST)\r
- glDisable(GL_LIGHTING)\r
- glColor3f(1,1,1)\r
- glPushMatrix()\r
- modelScale = profile.getProfileSettingFloat('model_scale')\r
- glScalef(modelScale, modelScale, modelScale)\r
- opengl.DrawMeshOutline(obj.mesh)\r
- glPopMatrix()\r
- \r
- if self.drawSteepOverhang:\r
- glDisable(GL_LIGHTING)\r
- glColor3f(1,1,1)\r
- glPushMatrix()\r
- modelScale = profile.getProfileSettingFloat('model_scale')\r
- glScalef(modelScale, modelScale, modelScale)\r
- glCallList(obj.steepDisplayList)\r
- glPopMatrix()\r
- \r
- glPopMatrix() \r
- if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":\r
- glDisable(GL_LIGHTING)\r
- glDisable(GL_DEPTH_TEST)\r
- glDisable(GL_BLEND)\r
- glColor3f(1,0,0)\r
- glBegin(GL_LINES)\r
- for err in self.parent.errorList:\r
- glVertex3f(err[0].x, err[0].y, err[0].z)\r
- glVertex3f(err[1].x, err[1].y, err[1].z)\r
- glEnd()\r
- glEnable(GL_DEPTH_TEST)\r
-\r
- glPushMatrix()\r
- glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
- \r
- #Draw the rotate circle\r
- if self.parent.objectsMaxV != None:\r
- glDisable(GL_LIGHTING)\r
- glDisable(GL_CULL_FACE)\r
- glEnable(GL_BLEND)\r
- glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)\r
- radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')\r
- glScalef(radius, radius, 1)\r
- glBegin(GL_TRIANGLE_STRIP)\r
- for i in xrange(0, 64+1):\r
- f = i if i < 64/2 else 64 - i\r
- glColor4ub(255,int(f*255/(64/2)),0,255)\r
- glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)\r
- glColor4ub( 0,128,0,255)\r
- glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)\r
- glEnd()\r
- glBegin(GL_TRIANGLES)\r
- glColor4ub(0,0,0,192)\r
- glVertex3f(1, 0.1,0.15)\r
- glVertex3f(1,-0.1,0.15)\r
- glVertex3f(1.4,0,0.15)\r
- glEnd()\r
- glEnable(GL_CULL_FACE)\r
- \r
- glPopMatrix()\r
-\r
- opengl.DrawMachine(machineSize)\r
- \r
- glFlush()\r
- \r
- def drawModel(self, obj):\r
- multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))\r
- multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))\r
- modelScale = profile.getProfileSettingFloat('model_scale')\r
- modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale\r
- glPushMatrix()\r
- glRotate(self.tempRotate, 0, 0, 1)\r
- glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)\r
- for mx in xrange(0, multiX):\r
- for my in xrange(0, multiY):\r
- glPushMatrix()\r
- glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)\r
- glScalef(modelScale, modelScale, modelScale)\r
- glCallList(obj.displayList)\r
- glPopMatrix()\r
- glPopMatrix()\r
-\r
+from __future__ import division
+
+import sys, math, threading, re, time, os
+import numpy
+
+from wx import glcanvas
+import wx
+try:
+ import OpenGL
+ OpenGL.ERROR_CHECKING = False
+ from OpenGL.GLU import *
+ from OpenGL.GL import *
+ hasOpenGLlibs = True
+except:
+ print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
+ hasOpenGLlibs = False
+
+from gui import opengl
+from gui import toolbarUtil
+
+from util import profile
+from util import gcodeInterpreter
+from util import meshLoader
+from util import util3d
+from util import sliceRun
+
+class previewObject():
+ def __init__(self):
+ self.mesh = None
+ self.filename = None
+ self.displayList = None
+ self.dirty = False
+
+class previewPanel(wx.Panel):
+ def __init__(self, parent):
+ super(previewPanel, self).__init__(parent,-1)
+
+ self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
+ self.SetMinSize((440,320))
+
+ self.objectList = []
+ self.errorList = []
+ self.gcode = None
+ self.objectsMinV = None
+ self.objectsMaxV = None
+ self.objectsBounderyCircleSize = None
+ self.loadThread = None
+ self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
+ self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
+
+ self.glCanvas = PreviewGLCanvas(self)
+ #Create the popup window
+ self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
+ self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
+ self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
+ self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
+ self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
+ self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.warningPopup.SetSizer(self.warningPopup.sizer)
+ self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
+ self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
+ self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
+ self.warningPopup.Fit()
+ self.warningPopup.Layout()
+ self.warningPopup.timer = wx.Timer(self)
+ self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
+
+ self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
+ self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
+ parent.Bind(wx.EVT_MOVE, self.OnMove)
+ parent.Bind(wx.EVT_SIZE, self.OnMove)
+
+ self.toolbar = toolbarUtil.Toolbar(self)
+
+ group = []
+ toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
+ toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
+ self.toolbar.AddSeparator()
+
+ self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
+ self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
+ self.toolbar.AddSeparator()
+
+ group = []
+ self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
+ self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
+ self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
+ self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
+ self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
+ self.toolbar.AddSeparator()
+
+ self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
+ self.toolbar.AddControl(self.layerSpin)
+ self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
+ self.toolbar.AddSeparator()
+ self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)
+ self.toolbar.AddControl(self.toolbarInfo)
+
+ self.toolbar2 = toolbarUtil.Toolbar(self)
+
+ # Mirror
+ self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)
+ self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)
+ self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)
+ self.toolbar2.AddSeparator()
+
+ # Swap
+ self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)
+ self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)
+ self.toolbar2.AddSeparator()
+
+ # Scale
+ self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
+ self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
+ self.toolbar2.AddControl(self.scale)
+ self.scale.Bind(wx.EVT_TEXT, self.OnScale)
+ self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
+
+ self.toolbar2.AddSeparator()
+
+ # Multiply
+ #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
+ #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
+ #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
+ #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
+ #self.toolbar2.AddSeparator()
+
+ # Rotate
+ self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
+ self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
+ self.rotate.SetRange(0, 360)
+ self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
+ self.toolbar2.AddControl(self.rotate)
+
+ self.toolbar2.Realize()
+ self.OnViewChange()
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
+ sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
+ sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
+ self.SetSizer(sizer)
+
+ def returnToModelViewAndUpdateModel(self):
+ if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
+ self.setViewMode('Normal')
+ self.updateModelTransform()
+
+ def OnMove(self, e = None):
+ if e != None:
+ e.Skip()
+ x, y = self.glCanvas.ClientToScreenXY(0, 0)
+ sx, sy = self.glCanvas.GetClientSizeTuple()
+ self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
+
+ def OnMulXAddClick(self, e):
+ profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
+ self.glCanvas.Refresh()
+
+ def OnMulXSubClick(self, e):
+ profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
+ self.glCanvas.Refresh()
+
+ def OnMulYAddClick(self, e):
+ profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
+ self.glCanvas.Refresh()
+
+ def OnMulYSubClick(self, e):
+ profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
+ self.glCanvas.Refresh()
+
+ def OnScaleReset(self, e):
+ self.scale.SetValue('1.0')
+ self.OnScale(None)
+
+ def OnScale(self, e):
+ scale = 1.0
+ if self.scale.GetValue() != '':
+ scale = self.scale.GetValue()
+ profile.putProfileSetting('model_scale', scale)
+ if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
+ self.setViewMode('Normal')
+ self.glCanvas.Refresh()
+
+ if self.objectsMaxV != None:
+ size = (self.objectsMaxV - self.objectsMinV) * float(scale)
+ self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
+
+ def OnScaleMax(self, e = None, onlyScaleDown = False):
+ if self.objectsMinV == None:
+ return
+ vMin = self.objectsMinV
+ vMax = self.objectsMaxV
+ skirtSize = 3
+ if profile.getProfileSettingFloat('skirt_line_count') > 0:
+ skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
+ scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
+ scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
+ scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
+ scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
+ scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
+ scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
+ if scale > 1.0 and onlyScaleDown:
+ return
+ self.scale.SetValue(str(scale))
+ profile.putProfileSetting('model_scale', self.scale.GetValue())
+ if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
+ self.setViewMode('Normal')
+ self.glCanvas.Refresh()
+
+ def OnRotateReset(self, e):
+ self.rotate.SetValue(0)
+ self.OnRotate(None)
+
+ def OnRotate(self, e):
+ profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
+ self.returnToModelViewAndUpdateModel()
+
+ def On3DClick(self):
+ self.glCanvas.yaw = 30
+ self.glCanvas.pitch = 60
+ self.glCanvas.zoom = 300
+ self.glCanvas.view3D = True
+ self.glCanvas.Refresh()
+
+ def OnTopClick(self):
+ self.glCanvas.view3D = False
+ self.glCanvas.zoom = 100
+ self.glCanvas.offsetX = 0
+ self.glCanvas.offsetY = 0
+ self.glCanvas.Refresh()
+
+ def OnLayerNrChange(self, e):
+ self.glCanvas.Refresh()
+
+ def setViewMode(self, mode):
+ if mode == "Normal":
+ self.normalViewButton.SetValue(True)
+ if mode == "GCode":
+ self.gcodeViewButton.SetValue(True)
+ self.glCanvas.viewMode = mode
+ wx.CallAfter(self.glCanvas.Refresh)
+
+ def loadModelFiles(self, filelist, showWarning = False):
+ while len(filelist) > len(self.objectList):
+ self.objectList.append(previewObject())
+ for idx in xrange(len(filelist), len(self.objectList)):
+ self.objectList[idx].mesh = None
+ self.objectList[idx].filename = None
+ for idx in xrange(0, len(filelist)):
+ obj = self.objectList[idx]
+ if obj.filename != filelist[idx]:
+ obj.fileTime = None
+ self.gcodeFileTime = None
+ self.logFileTime = None
+ obj.filename = filelist[idx]
+
+ self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
+ #Do the STL file loading in a background thread so we don't block the UI.
+ if self.loadThread != None and self.loadThread.isAlive():
+ self.loadThread.join()
+ self.loadThread = threading.Thread(target=self.doFileLoadThread)
+ self.loadThread.daemon = True
+ self.loadThread.start()
+
+ if showWarning:
+ if profile.getProfileSettingFloat('model_scale') != 1.0 or profile.getProfileSettingFloat('model_rotate_base') != 0 or profile.getProfileSetting('flip_x') != 'False' or profile.getProfileSetting('flip_y') != 'False' or profile.getProfileSetting('flip_z') != 'False' or profile.getProfileSetting('swap_xz') != 'False' or profile.getProfileSetting('swap_yz') != 'False' or len(profile.getPluginConfig()) > 0:
+ self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
+
+ def loadReModelFiles(self, filelist):
+ #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
+ for idx in xrange(0, len(filelist)):
+ if self.objectList[idx].filename != filelist[idx]:
+ return False
+ self.loadModelFiles(filelist)
+ return True
+
+ def doFileLoadThread(self):
+ for obj in self.objectList:
+ if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
+ obj.ileTime = os.stat(obj.filename).st_mtime
+ mesh = meshLoader.loadMesh(obj.filename)
+ obj.dirty = False
+ obj.mesh = mesh
+ self.updateModelTransform()
+ self.OnScaleMax(None, True)
+ scale = profile.getProfileSettingFloat('model_scale')
+ size = (self.objectsMaxV - self.objectsMinV) * scale
+ self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
+ self.glCanvas.zoom = numpy.max(size) * 2.5
+ self.errorList = []
+ wx.CallAfter(self.updateToolbar)
+ wx.CallAfter(self.glCanvas.Refresh)
+
+ if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
+ self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
+ gcode = gcodeInterpreter.gcode()
+ gcode.progressCallback = self.loadProgress
+ gcode.load(self.gcodeFilename)
+ self.gcodeDirty = False
+ self.gcode = gcode
+ self.gcodeDirty = True
+
+ errorList = []
+ for line in open(self.gcodeFilename, "rt"):
+ res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)
+ if res != None:
+ v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
+ v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
+ errorList.append([v1, v2])
+ self.errorList = errorList
+
+ wx.CallAfter(self.updateToolbar)
+ wx.CallAfter(self.glCanvas.Refresh)
+ elif not os.path.isfile(self.gcodeFilename):
+ self.gcode = None
+
+ def loadProgress(self, progress):
+ pass
+
+ def OnResetAll(self, e = None):
+ profile.putProfileSetting('model_scale', '1.0')
+ profile.putProfileSetting('model_rotate_base', '0')
+ profile.putProfileSetting('flip_x', 'False')
+ profile.putProfileSetting('flip_y', 'False')
+ profile.putProfileSetting('flip_z', 'False')
+ profile.putProfileSetting('swap_xz', 'False')
+ profile.putProfileSetting('swap_yz', 'False')
+ profile.setPluginConfig([])
+ self.GetParent().updateProfileToControls()
+
+ def ShowWarningPopup(self, text, callback = None):
+ self.warningPopup.text.SetLabel(text)
+ self.warningPopup.callback = callback
+ if callback == None:
+ self.warningPopup.yesButton.Show(False)
+ self.warningPopup.noButton.SetLabel('ok')
+ else:
+ self.warningPopup.yesButton.Show(True)
+ self.warningPopup.noButton.SetLabel('no')
+ self.warningPopup.Fit()
+ self.warningPopup.Layout()
+ self.OnMove()
+ self.warningPopup.Show(True)
+ self.warningPopup.timer.Start(5000)
+
+ def OnWarningPopup(self, e):
+ self.warningPopup.Show(False)
+ self.warningPopup.timer.Stop()
+ self.warningPopup.callback()
+
+ def OnHideWarning(self, e):
+ self.warningPopup.Show(False)
+ self.warningPopup.timer.Stop()
+
+ def updateToolbar(self):
+ self.gcodeViewButton.Show(self.gcode != None)
+ self.mixedViewButton.Show(self.gcode != None)
+ self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
+ if self.gcode != None:
+ self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
+ self.toolbar.Realize()
+ self.Update()
+
+ def OnViewChange(self):
+ if self.normalViewButton.GetValue():
+ self.glCanvas.viewMode = "Normal"
+ elif self.transparentViewButton.GetValue():
+ self.glCanvas.viewMode = "Transparent"
+ elif self.xrayViewButton.GetValue():
+ self.glCanvas.viewMode = "X-Ray"
+ elif self.gcodeViewButton.GetValue():
+ self.glCanvas.viewMode = "GCode"
+ elif self.mixedViewButton.GetValue():
+ self.glCanvas.viewMode = "Mixed"
+ self.glCanvas.drawBorders = self.showBorderButton.GetValue()
+ self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
+ self.updateToolbar()
+ self.glCanvas.Refresh()
+
+ def updateModelTransform(self, f=0):
+ if len(self.objectList) < 1 or self.objectList[0].mesh == None:
+ return
+
+ rotate = profile.getProfileSettingFloat('model_rotate_base')
+ mirrorX = profile.getProfileSetting('flip_x') == 'True'
+ mirrorY = profile.getProfileSetting('flip_y') == 'True'
+ mirrorZ = profile.getProfileSetting('flip_z') == 'True'
+ swapXZ = profile.getProfileSetting('swap_xz') == 'True'
+ swapYZ = profile.getProfileSetting('swap_yz') == 'True'
+
+ for obj in self.objectList:
+ if obj.mesh == None:
+ continue
+ obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
+
+ minV = self.objectList[0].mesh.getMinimum()
+ maxV = self.objectList[0].mesh.getMaximum()
+ objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize
+ for obj in self.objectList:
+ if obj.mesh == None:
+ continue
+
+ obj.mesh.getMinimumZ()
+ minV = numpy.minimum(minV, obj.mesh.getMinimum())
+ maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
+ objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)
+
+ self.objectsMaxV = maxV
+ self.objectsMinV = minV
+ self.objectsBounderyCircleSize = objectsBounderyCircleSize
+ for obj in self.objectList:
+ if obj.mesh == None:
+ continue
+
+ obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
+ #for v in obj.mesh.vertexes:
+ # v[2] -= minV[2]
+ # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
+ # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
+ obj.mesh.getMinimumZ()
+ obj.dirty = True
+
+ scale = profile.getProfileSettingFloat('model_scale')
+ size = (self.objectsMaxV - self.objectsMinV) * scale
+ self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
+
+ self.glCanvas.Refresh()
+
+ def updateProfileToControls(self):
+ self.scale.SetValue(profile.getProfileSetting('model_scale'))
+ self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
+ self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
+ self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
+ self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
+ self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
+ self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
+ self.updateModelTransform()
+ self.glCanvas.updateProfileToControls()
+
+class PreviewGLCanvas(glcanvas.GLCanvas):
+ def __init__(self, parent):
+ attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
+ glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
+ self.parent = parent
+ self.context = glcanvas.GLContext(self)
+ wx.EVT_PAINT(self, self.OnPaint)
+ wx.EVT_SIZE(self, self.OnSize)
+ wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
+ wx.EVT_MOTION(self, self.OnMouseMotion)
+ wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
+ self.yaw = 30
+ self.pitch = 60
+ self.zoom = 300
+ self.offsetX = 0
+ self.offsetY = 0
+ self.view3D = True
+ self.gcodeDisplayList = None
+ self.gcodeDisplayListMade = None
+ self.gcodeDisplayListCount = 0
+ self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]
+ self.oldX = 0
+ self.oldY = 0
+ self.dragType = ''
+ self.tempRotate = 0
+
+ def updateProfileToControls(self):
+ self.objColor[0] = profile.getPreferenceColour('model_colour')
+ self.objColor[1] = profile.getPreferenceColour('model_colour2')
+ self.objColor[2] = profile.getPreferenceColour('model_colour3')
+ self.objColor[3] = profile.getPreferenceColour('model_colour4')
+
+ def OnMouseMotion(self,e):
+ cursorXY = 100000
+ radius = 0
+ if self.parent.objectsMaxV != None:
+ radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
+
+ p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
+ p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
+ cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
+ cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
+ if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
+ self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
+ else:
+ self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+ if e.Dragging() and e.LeftIsDown():
+ if self.dragType == '':
+ #Define the drag type depending on the cursor position.
+ if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
+ self.dragType = 'modelRotate'
+ self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
+ else:
+ self.dragType = 'viewRotate'
+
+ if self.dragType == 'viewRotate':
+ if self.view3D:
+ self.yaw += e.GetX() - self.oldX
+ self.pitch -= e.GetY() - self.oldY
+ if self.pitch > 170:
+ self.pitch = 170
+ if self.pitch < 10:
+ self.pitch = 10
+ else:
+ self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
+ self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
+ elif self.dragType == 'modelRotate':
+ angle = math.atan2(cursorZ0[0], cursorZ0[1])
+ diff = self.dragStart - angle
+ self.tempRotate = diff * 180 / math.pi
+ rot = profile.getProfileSettingFloat('model_rotate_base')
+ self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot
+ #Workaround for buggy ATI cards.
+ size = self.GetSizeTuple()
+ self.SetSize((size[0]+1, size[1]))
+ self.SetSize((size[0], size[1]))
+ self.Refresh()
+ else:
+ if self.tempRotate != 0:
+ newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate
+ while newRotation >= 360:
+ newRotation -= 360
+ while newRotation < 0:
+ newRotation += 360
+ profile.putProfileSetting('model_rotate_base', newRotation)
+ self.parent.rotate.SetValue(newRotation)
+ self.parent.updateModelTransform()
+ self.tempRotate = 0
+
+ self.dragType = ''
+ if e.Dragging() and e.RightIsDown():
+ self.zoom += e.GetY() - self.oldY
+ if self.zoom < 1:
+ self.zoom = 1
+ if self.zoom > 500:
+ self.zoom = 500
+ self.Refresh()
+ self.oldX = e.GetX()
+ self.oldY = e.GetY()
+
+ #self.Refresh()
+
+
+ def OnMouseWheel(self,e):
+ self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
+ if self.zoom < 1.0:
+ self.zoom = 1.0
+ if self.zoom > 500:
+ self.zoom = 500
+ self.Refresh()
+
+ def OnEraseBackground(self,event):
+ #Workaround for windows background redraw flicker.
+ pass
+
+ def OnSize(self,e):
+ self.Refresh()
+
+ def OnPaint(self,e):
+ dc = wx.PaintDC(self)
+ if not hasOpenGLlibs:
+ dc.Clear()
+ dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
+ return
+ self.SetCurrent(self.context)
+ opengl.InitGL(self, self.view3D, self.zoom)
+ if self.view3D:
+ glTranslate(0,0,-self.zoom)
+ glRotate(-self.pitch, 1,0,0)
+ glRotate(self.yaw, 0,0,1)
+ if self.viewMode == "GCode" or self.viewMode == "Mixed":
+ if self.parent.gcode != None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:
+ glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
+ else:
+ if self.parent.objectsMaxV != None:
+ glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
+ else:
+ glTranslate(self.offsetX, self.offsetY, 0)
+
+ self.viewport = glGetIntegerv(GL_VIEWPORT);
+ self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
+ self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
+
+ glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
+
+ self.OnDraw()
+ self.SwapBuffers()
+
+ def OnDraw(self):
+ machineSize = self.parent.machineSize
+
+ if self.parent.gcode != None and self.parent.gcodeDirty:
+ if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
+ if self.gcodeDisplayList != None:
+ glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
+ self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
+ self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
+ self.parent.gcodeDirty = False
+ self.gcodeDisplayListMade = 0
+
+ if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
+ glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
+ opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
+ glEndList()
+ self.gcodeDisplayListMade += 1
+ wx.CallAfter(self.Refresh)
+
+ glPushMatrix()
+ glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
+ for obj in self.parent.objectList:
+ if obj.mesh == None:
+ continue
+ if obj.displayList == None:
+ obj.displayList = glGenLists(1)
+ obj.steepDisplayList = glGenLists(1)
+ if obj.dirty:
+ obj.dirty = False
+ glNewList(obj.displayList, GL_COMPILE)
+ opengl.DrawMesh(obj.mesh)
+ glEndList()
+ glNewList(obj.steepDisplayList, GL_COMPILE)
+ opengl.DrawMeshSteep(obj.mesh, 60)
+ glEndList()
+
+ if self.viewMode == "Mixed":
+ glDisable(GL_BLEND)
+ glColor3f(0.0,0.0,0.0)
+ self.drawModel(obj)
+ glColor3f(1.0,1.0,1.0)
+ glClear(GL_DEPTH_BUFFER_BIT)
+
+ glPopMatrix()
+
+ if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
+ glEnable(GL_COLOR_MATERIAL)
+ glEnable(GL_LIGHTING)
+ drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
+ starttime = time.time()
+ for i in xrange(drawUpToLayer - 1, -1, -1):
+ c = 1.0
+ if i < self.parent.layerSpin.GetValue():
+ c = 0.9 - (drawUpToLayer - i) * 0.1
+ if c < 0.4:
+ c = (0.4 + c) / 2
+ if c < 0.1:
+ c = 0.1
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
+ glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
+ glCallList(self.gcodeDisplayList + i)
+ if time.time() - starttime > 0.1:
+ break
+
+ glDisable(GL_LIGHTING)
+ glDisable(GL_COLOR_MATERIAL)
+ glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
+ glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
+
+ glColor3f(1.0,1.0,1.0)
+ glPushMatrix()
+ glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
+ for obj in self.parent.objectList:
+ if obj.mesh == None:
+ continue
+
+ if self.viewMode == "Transparent" or self.viewMode == "Mixed":
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
+ glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
+ #If we want transparent, then first render a solid black model to remove the printer size lines.
+ if self.viewMode != "Mixed":
+ glDisable(GL_BLEND)
+ glColor3f(0.0,0.0,0.0)
+ self.drawModel(obj)
+ glColor3f(1.0,1.0,1.0)
+ #After the black model is rendered, render the model again but now with lighting and no depth testing.
+ glDisable(GL_DEPTH_TEST)
+ glEnable(GL_LIGHTING)
+ glEnable(GL_BLEND)
+ glBlendFunc(GL_ONE, GL_ONE)
+ glEnable(GL_LIGHTING)
+ self.drawModel(obj)
+ glEnable(GL_DEPTH_TEST)
+ elif self.viewMode == "X-Ray":
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
+ glDisable(GL_LIGHTING)
+ glDisable(GL_DEPTH_TEST)
+ glEnable(GL_STENCIL_TEST)
+ glStencilFunc(GL_ALWAYS, 1, 1)
+ glStencilOp(GL_INCR, GL_INCR, GL_INCR)
+ self.drawModel(obj)
+ glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
+
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
+ glStencilFunc(GL_EQUAL, 0, 1)
+ glColor(1, 1, 1)
+ self.drawModel(obj)
+ glStencilFunc(GL_EQUAL, 1, 1)
+ glColor(1, 0, 0)
+ self.drawModel(obj)
+
+ glPushMatrix()
+ glLoadIdentity()
+ for i in xrange(2, 15, 2):
+ glStencilFunc(GL_EQUAL, i, 0xFF);
+ glColor(float(i)/10, float(i)/10, float(i)/5)
+ glBegin(GL_QUADS)
+ glVertex3f(-1000,-1000,-1)
+ glVertex3f( 1000,-1000,-1)
+ glVertex3f( 1000, 1000,-1)
+ glVertex3f(-1000, 1000,-1)
+ glEnd()
+ for i in xrange(1, 15, 2):
+ glStencilFunc(GL_EQUAL, i, 0xFF);
+ glColor(float(i)/10, 0, 0)
+ glBegin(GL_QUADS)
+ glVertex3f(-1000,-1000,-1)
+ glVertex3f( 1000,-1000,-1)
+ glVertex3f( 1000, 1000,-1)
+ glVertex3f(-1000, 1000,-1)
+ glEnd()
+ glPopMatrix()
+
+ glDisable(GL_STENCIL_TEST)
+ glEnable(GL_DEPTH_TEST)
+
+ #Fix the depth buffer
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
+ self.drawModel(obj)
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
+ elif self.viewMode == "Normal":
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
+ glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
+ glEnable(GL_LIGHTING)
+ self.drawModel(obj)
+
+ if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
+ glEnable(GL_DEPTH_TEST)
+ glDisable(GL_LIGHTING)
+ glColor3f(1,1,1)
+ glPushMatrix()
+ modelScale = profile.getProfileSettingFloat('model_scale')
+ glScalef(modelScale, modelScale, modelScale)
+ opengl.DrawMeshOutline(obj.mesh)
+ glPopMatrix()
+
+ if self.drawSteepOverhang:
+ glDisable(GL_LIGHTING)
+ glColor3f(1,1,1)
+ glPushMatrix()
+ modelScale = profile.getProfileSettingFloat('model_scale')
+ glScalef(modelScale, modelScale, modelScale)
+ glCallList(obj.steepDisplayList)
+ glPopMatrix()
+
+ glPopMatrix()
+ if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
+ glDisable(GL_LIGHTING)
+ glDisable(GL_DEPTH_TEST)
+ glDisable(GL_BLEND)
+ glColor3f(1,0,0)
+ glBegin(GL_LINES)
+ for err in self.parent.errorList:
+ glVertex3f(err[0].x, err[0].y, err[0].z)
+ glVertex3f(err[1].x, err[1].y, err[1].z)
+ glEnd()
+ glEnable(GL_DEPTH_TEST)
+
+ glPushMatrix()
+ glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
+
+ #Draw the rotate circle
+ if self.parent.objectsMaxV != None:
+ glDisable(GL_LIGHTING)
+ glDisable(GL_CULL_FACE)
+ glEnable(GL_BLEND)
+ glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)
+ radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
+ glScalef(radius, radius, 1)
+ glBegin(GL_TRIANGLE_STRIP)
+ for i in xrange(0, 64+1):
+ f = i if i < 64/2 else 64 - i
+ glColor4ub(255,int(f*255/(64/2)),0,255)
+ glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)
+ glColor4ub( 0,128,0,255)
+ glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)
+ glEnd()
+ glBegin(GL_TRIANGLES)
+ glColor4ub(0,0,0,192)
+ glVertex3f(1, 0.1,0.15)
+ glVertex3f(1,-0.1,0.15)
+ glVertex3f(1.4,0,0.15)
+ glEnd()
+ glEnable(GL_CULL_FACE)
+
+ glPopMatrix()
+
+ opengl.DrawMachine(machineSize)
+
+ glFlush()
+
+ def drawModel(self, obj):
+ multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
+ multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
+ modelScale = profile.getProfileSettingFloat('model_scale')
+ modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
+ glPushMatrix()
+ glRotate(self.tempRotate, 0, 0, 1)
+ glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
+ for mx in xrange(0, multiX):
+ for my in xrange(0, multiY):
+ glPushMatrix()
+ glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
+ glScalef(modelScale, modelScale, modelScale)
+ glCallList(obj.displayList)
+ glPopMatrix()
+ glPopMatrix()
+