OctoPrint/Cura/gui/preview3d.py

799 lines
32 KiB
Python

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.loadThread = None
self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
self.machineCenter = util3d.Vector3(float(profile.getProfileSetting('machine_center_x')), float(profile.getProfileSetting('machine_center_y')), 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, '', 'view-border-on.png', 'view-border-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.updateModelTransform)
self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.updateModelTransform)
self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.updateModelTransform)
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.updateModelTransform)
self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.updateModelTransform)
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 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)
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())
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.updateModelTransform()
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 updateCenterX(self):
self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')
self.glCanvas.Refresh()
def updateCenterY(self):
self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')
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()
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())
self.objectsMaxV = maxV
self.objectsMinV = minV
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
sizeXY = 0
if self.parent.objectsMaxV != None:
size = (self.parent.objectsMaxV - self.parent.objectsMinV)
sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
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 >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
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 >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
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
#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:
profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)
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)
opengl.DrawMachine(machineSize)
glPushMatrix()
glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
#Draw the rotate circle
if self.parent.objectsMaxV != None and False:
glDisable(GL_LIGHTING)
glDisable(GL_CULL_FACE)
glEnable(GL_BLEND)
glBegin(GL_TRIANGLE_STRIP)
size = (self.parent.objectsMaxV - self.parent.objectsMinV)
sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
for i in xrange(0, 64+1):
f = i if i < 64/2 else 64 - i
glColor4ub(255,int(f*255/(64/2)),0,128)
glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)
glColor4ub( 0,128,0,128)
glVertex3f((sizeXY * 0.7 + 3) * math.cos(i/32.0*math.pi), (sizeXY * 0.7 + 3) * math.sin(i/32.0*math.pi),0.1)
glEnd()
glEnable(GL_CULL_FACE)
glPopMatrix()
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()