497 lines
16 KiB
Python
497 lines
16 KiB
Python
import sys
|
|
import math
|
|
import threading
|
|
import re
|
|
|
|
from wx import glcanvas
|
|
import wx
|
|
try:
|
|
from OpenGL.GLU import *
|
|
from OpenGL.GL import *
|
|
hasOpenGLlibs = True
|
|
except:
|
|
print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
|
|
hasOpenGLlibs = False
|
|
|
|
from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
|
|
from fabmetheus_utilities.vector3 import Vector3
|
|
from fabmetheus_utilities import settings
|
|
from newui import gcodeInterpreter
|
|
|
|
class previewPanel(wx.Panel):
|
|
def __init__(self, parent):
|
|
wx.Panel.__init__(self, parent,-1)
|
|
|
|
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
|
|
self.SetMinSize((400,300))
|
|
|
|
self.glCanvas = PreviewGLCanvas(self)
|
|
self.init = 0
|
|
self.triangleMesh = None
|
|
self.gcode = None
|
|
self.machineSize = Vector3(float(settings.getPreference('machine_width', '205')), float(settings.getPreference('machine_depth', '205')), float(settings.getPreference('machine_height', '200')))
|
|
self.machineCenter = Vector3(0, 0, 0)
|
|
|
|
self.toolbar = wx.ToolBar( self, -1 )
|
|
self.toolbar.SetToolBitmapSize( ( 21, 21 ) )
|
|
|
|
button = wx.Button(self.toolbar, -1, "3D", size=(21*2,21))
|
|
self.toolbar.AddControl(button)
|
|
self.Bind(wx.EVT_BUTTON, self.On3DClick, button)
|
|
|
|
button = wx.Button(self.toolbar, -1, "Top", size=(21*2,21))
|
|
self.toolbar.AddControl(button)
|
|
self.Bind(wx.EVT_BUTTON, self.OnTopClick, button)
|
|
|
|
self.transparentButton = wx.Button(self.toolbar, -1, "T", size=(21,21))
|
|
self.toolbar.AddControl(self.transparentButton)
|
|
self.Bind(wx.EVT_BUTTON, self.OnTransparentClick, self.transparentButton)
|
|
self.XRayButton = wx.Button(self.toolbar, -1, "X-RAY", size=(21*2,21))
|
|
self.toolbar.AddControl(self.XRayButton)
|
|
self.Bind(wx.EVT_BUTTON, self.OnXRayClick, self.XRayButton)
|
|
|
|
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.updateToolbar()
|
|
|
|
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)
|
|
self.SetSizer(sizer)
|
|
|
|
def On3DClick(self, e):
|
|
self.glCanvas.yaw = 30
|
|
self.glCanvas.pitch = 60
|
|
self.glCanvas.zoom = 150
|
|
self.glCanvas.view3D = True
|
|
self.glCanvas.Refresh()
|
|
|
|
def OnTopClick(self, e):
|
|
self.glCanvas.view3D = False
|
|
self.glCanvas.zoom = 100
|
|
self.glCanvas.offsetX = 0
|
|
self.glCanvas.offsetY = 0
|
|
self.glCanvas.Refresh()
|
|
|
|
def OnLayerNrChange(self, e):
|
|
self.modelDirty = True
|
|
self.glCanvas.Refresh()
|
|
|
|
def updateCenterX(self, x):
|
|
self.machineCenter.x = x
|
|
self.moveModel()
|
|
self.glCanvas.Refresh()
|
|
|
|
def updateCenterY(self, y):
|
|
self.machineCenter.y = y
|
|
self.moveModel()
|
|
self.glCanvas.Refresh()
|
|
|
|
def updateWallLineWidth(self, setting):
|
|
self.glCanvas.lineWidth = settings.calculateEdgeWidth(setting)
|
|
|
|
def updateInfillLineWidth(self, setting):
|
|
self.glCanvas.infillLineWidth = settings.getProfileSetting('nozzle_size')
|
|
|
|
def loadModelFile(self, filename):
|
|
self.modelFilename = filename
|
|
#Do the STL file loading in a background thread so we don't block the UI.
|
|
thread = threading.Thread(target=self.DoModelLoad)
|
|
thread.start()
|
|
|
|
def loadGCodeFile(self, filename):
|
|
self.gcodeFilename = filename
|
|
#Do the STL file loading in a background thread so we don't block the UI.
|
|
thread = threading.Thread(target=self.DoGCodeLoad)
|
|
thread.start()
|
|
|
|
def DoModelLoad(self):
|
|
self.modelDirty = False
|
|
triangleMesh = fabmetheus_interpret.getCarving(self.modelFilename)
|
|
triangleMesh.origonalVertexes = list(triangleMesh.vertexes)
|
|
for i in xrange(0, len(triangleMesh.origonalVertexes)):
|
|
triangleMesh.origonalVertexes[i] = triangleMesh.origonalVertexes[i].copy()
|
|
triangleMesh.getMinimumZ()
|
|
self.triangleMesh = triangleMesh
|
|
self.gcode = None
|
|
self.updateModelTransform()
|
|
wx.CallAfter(self.updateToolbar)
|
|
wx.CallAfter(self.glCanvas.Refresh)
|
|
|
|
def DoGCodeLoad(self):
|
|
gcode = gcodeInterpreter.gcode(self.gcodeFilename)
|
|
self.modelDirty = False
|
|
self.gcode = gcode
|
|
self.triangleMesh = None
|
|
self.modelDirty = True
|
|
wx.CallAfter(self.updateToolbar)
|
|
wx.CallAfter(self.glCanvas.Refresh)
|
|
|
|
def updateToolbar(self):
|
|
self.transparentButton.Show(self.triangleMesh != None)
|
|
self.XRayButton.Show(self.triangleMesh != None)
|
|
self.layerSpin.Show(self.gcode != None)
|
|
if self.gcode != None:
|
|
self.layerSpin.SetRange(1, self.gcode.layerCount)
|
|
self.toolbar.Realize()
|
|
|
|
def OnTransparentClick(self, e):
|
|
self.glCanvas.renderTransparent = not self.glCanvas.renderTransparent
|
|
if self.glCanvas.renderTransparent:
|
|
self.glCanvas.renderXRay = False
|
|
self.glCanvas.Refresh()
|
|
|
|
def OnXRayClick(self, e):
|
|
self.glCanvas.renderXRay = not self.glCanvas.renderXRay
|
|
if self.glCanvas.renderXRay:
|
|
self.glCanvas.renderTransparent = False
|
|
self.glCanvas.Refresh()
|
|
|
|
def updateModelTransform(self, f=0):
|
|
if self.triangleMesh == None:
|
|
return
|
|
for face in self.triangleMesh.faces:
|
|
face.normal = None
|
|
scale = 1.0
|
|
rotate = 0.0
|
|
try:
|
|
scale = float(settings.getProfileSetting('model_scale', '1.0'))
|
|
rotate = float(settings.getProfileSetting('model_rotate_base', '0.0')) / 180 * math.pi
|
|
except:
|
|
pass
|
|
scaleX = scale
|
|
scaleY = scale
|
|
scaleZ = scale
|
|
if settings.getProfileSetting('flip_x') == 'True':
|
|
scaleX = -scaleX
|
|
if settings.getProfileSetting('flip_y') == 'True':
|
|
scaleY = -scaleY
|
|
if settings.getProfileSetting('flip_z') == 'True':
|
|
scaleZ = -scaleZ
|
|
mat00 = math.cos(rotate) * scaleX
|
|
mat01 =-math.sin(rotate) * scaleY
|
|
mat10 = math.sin(rotate) * scaleX
|
|
mat11 = math.cos(rotate) * scaleY
|
|
|
|
for i in xrange(0, len(self.triangleMesh.origonalVertexes)):
|
|
self.triangleMesh.vertexes[i].x = self.triangleMesh.origonalVertexes[i].x * mat00 + self.triangleMesh.origonalVertexes[i].y * mat01
|
|
self.triangleMesh.vertexes[i].y = self.triangleMesh.origonalVertexes[i].x * mat10 + self.triangleMesh.origonalVertexes[i].y * mat11
|
|
self.triangleMesh.vertexes[i].z = self.triangleMesh.origonalVertexes[i].z * scaleZ
|
|
self.moveModel()
|
|
|
|
def moveModel(self):
|
|
if self.triangleMesh == None:
|
|
return
|
|
minZ = self.triangleMesh.getMinimumZ()
|
|
min = self.triangleMesh.getCarveCornerMinimum()
|
|
max = self.triangleMesh.getCarveCornerMaximum()
|
|
for v in self.triangleMesh.vertexes:
|
|
v.z -= minZ
|
|
v.x -= min.x + (max.x - min.x) / 2
|
|
v.y -= min.y + (max.y - min.y) / 2
|
|
v.x += self.machineCenter.x
|
|
v.y += self.machineCenter.y
|
|
self.triangleMesh.getMinimumZ()
|
|
self.modelDirty = True
|
|
self.glCanvas.Refresh()
|
|
|
|
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 = 150
|
|
self.offsetX = 0
|
|
self.offsetY = 0
|
|
self.lineWidth = 0.4
|
|
self.fillLineWidth = 0.4
|
|
self.view3D = True
|
|
self.renderTransparent = False
|
|
self.renderXRay = False
|
|
self.modelDisplayList = None
|
|
|
|
def OnMouseMotion(self,e):
|
|
if e.Dragging() and e.LeftIsDown():
|
|
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
|
|
self.Refresh()
|
|
if e.Dragging() and e.RightIsDown():
|
|
self.zoom += e.GetY() - self.oldY
|
|
if self.zoom < 1:
|
|
self.zoom = 1
|
|
self.Refresh()
|
|
self.oldX = e.GetX()
|
|
self.oldY = e.GetY()
|
|
|
|
def OnMouseWheel(self,e):
|
|
self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
|
|
if self.zoom < 1.0:
|
|
self.zoom = 1.0
|
|
self.Refresh()
|
|
|
|
def OnEraseBackground(self,event):
|
|
pass
|
|
|
|
def OnSize(self,event):
|
|
self.Refresh()
|
|
|
|
def OnPaint(self,event):
|
|
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)
|
|
self.InitGL()
|
|
self.OnDraw()
|
|
self.SwapBuffers()
|
|
|
|
def OnDraw(self):
|
|
machineSize = self.parent.machineSize
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
|
|
|
|
glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
|
|
|
|
glColor3f(1,1,1)
|
|
glLineWidth(4)
|
|
glDisable(GL_LIGHTING)
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0, 0)
|
|
glVertex3f(machineSize.x, 0, 0)
|
|
glVertex3f(machineSize.x, machineSize.y, 0)
|
|
glVertex3f(0, machineSize.y, 0)
|
|
glEnd()
|
|
glLineWidth(2)
|
|
glBegin(GL_LINES)
|
|
for i in xrange(0, int(machineSize.x), 10):
|
|
glVertex3f(i, 0, 0)
|
|
glVertex3f(i, machineSize.y, 0)
|
|
for i in xrange(0, int(machineSize.y), 10):
|
|
glVertex3f(0, i, 0)
|
|
glVertex3f(machineSize.x, i, 0)
|
|
glEnd()
|
|
glLineWidth(1)
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0, machineSize.z)
|
|
glVertex3f(machineSize.x, 0, machineSize.z)
|
|
glVertex3f(machineSize.x, machineSize.y, machineSize.z)
|
|
glVertex3f(0, machineSize.y, machineSize.z)
|
|
glEnd()
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0, 0, 0)
|
|
glVertex3f(0, 0, machineSize.z)
|
|
glVertex3f(machineSize.x, 0, 0)
|
|
glVertex3f(machineSize.x, 0, machineSize.z)
|
|
glVertex3f(machineSize.x, machineSize.y, 0)
|
|
glVertex3f(machineSize.x, machineSize.y, machineSize.z)
|
|
glVertex3f(0, machineSize.y, 0)
|
|
glVertex3f(0, machineSize.y, machineSize.z)
|
|
glEnd()
|
|
|
|
if self.parent.gcode != None:
|
|
if self.modelDisplayList == None:
|
|
self.modelDisplayList = glGenLists(1);
|
|
if self.parent.modelDirty:
|
|
self.parent.modelDirty = False
|
|
glNewList(self.modelDisplayList, GL_COMPILE)
|
|
for path in self.parent.gcode.pathList:
|
|
c = 1.0
|
|
if path['layerNr'] != self.parent.layerSpin.GetValue():
|
|
if path['layerNr'] < self.parent.layerSpin.GetValue():
|
|
c = 0.5 - (self.parent.layerSpin.GetValue() - path['layerNr']) * 0.1
|
|
if c < 0.1:
|
|
c = 0.1
|
|
else:
|
|
break
|
|
if path['type'] == 'move':
|
|
glColor3f(0,0,c)
|
|
if path['type'] == 'extrude':
|
|
if path['pathType'] == 'FILL':
|
|
glColor3f(c/2,c/2,0)
|
|
elif path['pathType'] == 'WALL-INNER':
|
|
glColor3f(0,c,0)
|
|
else:
|
|
glColor3f(c,0,0)
|
|
if path['type'] == 'retract':
|
|
glColor3f(0,c,c)
|
|
if c > 0.1 and path['type'] == 'extrude':
|
|
if path['pathType'] == 'FILL':
|
|
lineWidth = self.fillLineWidth / 2
|
|
else:
|
|
lineWidth = self.lineWidth / 2
|
|
for i in xrange(0, len(path['list'])-1):
|
|
v0 = path['list'][i]
|
|
v1 = path['list'][i+1]
|
|
normal = (v0 - v1).cross(Vector3(0,0,1))
|
|
normal.normalize()
|
|
v2 = v0 + normal * lineWidth
|
|
v3 = v1 + normal * lineWidth
|
|
v0 = v0 - normal * lineWidth
|
|
v1 = v1 - normal * lineWidth
|
|
|
|
glBegin(GL_QUADS)
|
|
glVertex3f(v0.x, v0.y, v0.z - 0.001)
|
|
glVertex3f(v1.x, v1.y, v1.z - 0.001)
|
|
glVertex3f(v3.x, v3.y, v3.z - 0.001)
|
|
glVertex3f(v2.x, v2.y, v2.z - 0.001)
|
|
glEnd()
|
|
for v in path['list']:
|
|
glBegin(GL_TRIANGLE_FAN)
|
|
glVertex3f(v.x, v.y, v.z - 0.001)
|
|
for i in xrange(0, 16+1):
|
|
glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.001)
|
|
glEnd()
|
|
else:
|
|
glBegin(GL_LINE_STRIP)
|
|
for v in path['list']:
|
|
glVertex3f(v.x, v.y, v.z)
|
|
glEnd()
|
|
glEndList()
|
|
glCallList(self.modelDisplayList)
|
|
|
|
if self.parent.triangleMesh != None:
|
|
if self.modelDisplayList == None:
|
|
self.modelDisplayList = glGenLists(1);
|
|
if self.parent.modelDirty:
|
|
self.parent.modelDirty = False
|
|
glNewList(self.modelDisplayList, GL_COMPILE)
|
|
glBegin(GL_TRIANGLES)
|
|
for face in self.parent.triangleMesh.faces:
|
|
v1 = self.parent.triangleMesh.vertexes[face.vertexIndexes[0]]
|
|
v2 = self.parent.triangleMesh.vertexes[face.vertexIndexes[1]]
|
|
v3 = self.parent.triangleMesh.vertexes[face.vertexIndexes[2]]
|
|
if face.normal == None:
|
|
face.normal = (v2 - v1).cross(v3 - v1)
|
|
face.normal.normalize()
|
|
glNormal3f(face.normal.x, face.normal.y, face.normal.z)
|
|
glVertex3f(v1.x, v1.y, v1.z)
|
|
glVertex3f(v2.x, v2.y, v2.z)
|
|
glVertex3f(v3.x, v3.y, v3.z)
|
|
glNormal3f(-face.normal.x, -face.normal.y, -face.normal.z)
|
|
glVertex3f(v1.x, v1.y, v1.z)
|
|
glVertex3f(v3.x, v3.y, v3.z)
|
|
glVertex3f(v2.x, v2.y, v2.z)
|
|
glEnd()
|
|
glEndList()
|
|
if self.renderTransparent:
|
|
#If we want transparent, then first render a solid black model to remove the printer size lines.
|
|
glDisable(GL_BLEND)
|
|
glDisable(GL_LIGHTING)
|
|
glColor3f(0,0,0)
|
|
glCallList(self.modelDisplayList)
|
|
glColor3f(1,1,1)
|
|
#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)
|
|
glCallList(self.modelDisplayList)
|
|
elif self.renderXRay:
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
|
|
glDisable(GL_DEPTH_TEST)
|
|
glEnable(GL_STENCIL_TEST);
|
|
glStencilFunc(GL_ALWAYS, 1, 1)
|
|
glStencilOp(GL_INCR, GL_INCR, GL_INCR)
|
|
glCallList(self.modelDisplayList)
|
|
glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
|
|
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
|
|
glStencilFunc(GL_EQUAL, 0, 1);
|
|
glColor(0, 1, 0)
|
|
glCallList(self.modelDisplayList)
|
|
glStencilFunc(GL_EQUAL, 1, 1);
|
|
glColor(1, 0, 0)
|
|
glCallList(self.modelDisplayList)
|
|
|
|
glPushMatrix()
|
|
glLoadIdentity()
|
|
for i in xrange(2, 20, 2):
|
|
glStencilFunc(GL_EQUAL, i, 0xFF);
|
|
glColor(0, float(i)/10, 0)
|
|
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, 20, 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)
|
|
else:
|
|
glEnable(GL_LIGHTING)
|
|
glCallList(self.modelDisplayList)
|
|
glFlush()
|
|
|
|
def InitGL(self):
|
|
# set viewing projection
|
|
glMatrixMode(GL_MODELVIEW)
|
|
glLoadIdentity()
|
|
size = self.GetSize()
|
|
glViewport(0,0, size.GetWidth(), size.GetHeight())
|
|
|
|
if self.renderTransparent:
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.5, 0.4, 0.3, 1.0])
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.1, 0.1, 0.1, 0.0])
|
|
else:
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 0.8, 0.6, 1.0])
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.2, 0.2, 0.2, 0.0])
|
|
glLightfv(GL_LIGHT0, GL_POSITION, [1.0, 1.0, 1.0, 0.0])
|
|
|
|
glEnable(GL_LIGHTING)
|
|
glEnable(GL_LIGHT0)
|
|
glEnable(GL_DEPTH_TEST)
|
|
glEnable(GL_CULL_FACE)
|
|
glDisable(GL_BLEND)
|
|
|
|
glClearColor(0.0, 0.0, 0.0, 1.0)
|
|
glClearStencil(0)
|
|
glClearDepth(1.0)
|
|
|
|
glMatrixMode(GL_PROJECTION)
|
|
glLoadIdentity()
|
|
aspect = float(self.GetSize().GetWidth()) / float(self.GetSize().GetHeight())
|
|
if self.view3D:
|
|
gluPerspective(90.0, aspect, 1.0, 1000.0)
|
|
else:
|
|
glOrtho(-self.zoom * aspect, self.zoom * aspect, -self.zoom, self.zoom, -1000.0, 1000.0)
|
|
|
|
glMatrixMode(GL_MODELVIEW)
|
|
glLoadIdentity()
|
|
if self.view3D:
|
|
glTranslate(0,0,-self.zoom)
|
|
glRotate(-self.pitch, 1,0,0)
|
|
glRotate(self.yaw, 0,0,1)
|
|
if self.parent.triangleMesh != None:
|
|
glTranslate(0,0,-self.parent.triangleMesh.getCarveCornerMaximum().z / 2)
|
|
else:
|
|
glTranslate(self.offsetX, self.offsetY, 0)
|
|
|