#!/usr/bin/python import os import wx,math,stltool from wx import glcanvas import time import threading import pyglet pyglet.options['shadow_window'] = False pyglet.options['debug_gl'] = False from pyglet import gl from pyglet.gl import * class GLPanel(wx.Panel): '''A simple class for using OpenGL with wxPython.''' def __init__(self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): # Forcing a no full repaint to stop flickering style = style | wx.NO_FULL_REPAINT_ON_RESIZE #call super function super(GLPanel, self).__init__(parent, id, pos, size, style) #init gl canvas data self.GLinitialized = False attribList = (glcanvas.WX_GL_RGBA, # RGBA glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit # Create the canvas self.sizer = wx.BoxSizer(wx.HORIZONTAL) self.canvas = glcanvas.GLCanvas(self, attribList=attribList) self.sizer.Add(self.canvas, 1, wx.EXPAND) self.SetSizer(self.sizer) #self.sizer.Fit(self) self.Layout() # bind events self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent) self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent) self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent) #========================================================================== # Canvas Proxy Methods #========================================================================== def GetGLExtents(self): '''Get the extents of the OpenGL canvas.''' return self.canvas.GetClientSize() def SwapBuffers(self): '''Swap the OpenGL buffers.''' self.canvas.SwapBuffers() #========================================================================== # wxPython Window Handlers #========================================================================== def processEraseBackgroundEvent(self, event): '''Process the erase background event.''' pass # Do nothing, to avoid flashing on MSWin def processSizeEvent(self, event): '''Process the resize event.''' if self.canvas.GetContext(): # Make sure the frame is shown before calling SetCurrent. self.Show() self.canvas.SetCurrent() size = self.GetGLExtents() self.winsize = (size.width, size.height) self.width, self.height = size.width, size.height self.OnReshape(size.width, size.height) self.canvas.Refresh(False) event.Skip() def processPaintEvent(self, event): '''Process the drawing event.''' self.canvas.SetCurrent() # This is a 'perfect' time to initialize OpenGL ... only if we need to if not self.GLinitialized: self.OnInitGL() self.GLinitialized = True self.OnDraw() event.Skip() def Destroy(self): #clean up the pyglet OpenGL context #self.pygletcontext.destroy() #call the super method super(wx.Panel, self).Destroy() #========================================================================== # GLFrame OpenGL Event Handlers #========================================================================== def OnInitGL(self): '''Initialize OpenGL for use in the window.''' #create a pyglet context for this panel self.pmat=(GLdouble * 16)() self.mvmat=(GLdouble * 16)() self.pygletcontext = Context(current_context) self.pygletcontext.set_current() self.dist=1000 self.vpmat=None #normal gl init glClearColor(0, 0, 0, 1) glColor3f(1, 0, 0) glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) # Uncomment this line for a wireframe view #glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) # Simple light setup. On Windows GL_LIGHT0 is enabled by default, # but this is not the case on Linux or Mac, so remember to always # include it. glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glEnable(GL_LIGHT1) # Define a simple function to create ctypes arrays of floats: def vec(*args): return (GLfloat * len(args))(*args) glLightfv(GL_LIGHT0, GL_POSITION, vec(.5, .5, 1, 0)) glLightfv(GL_LIGHT0, GL_SPECULAR, vec(.5, .5, 1, 1)) glLightfv(GL_LIGHT0, GL_DIFFUSE, vec(1, 1, 1, 1)) glLightfv(GL_LIGHT1, GL_POSITION, vec(1, 0, .5, 0)) glLightfv(GL_LIGHT1, GL_DIFFUSE, vec(.5, .5, .5, 1)) glLightfv(GL_LIGHT1, GL_SPECULAR, vec(1, 1, 1, 1)) glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec(0.5, 0, 0.3, 1)) glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, vec(1, 1, 1, 1)) glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50) glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, vec(0,0.1,0,0.9)) #create objects to draw #self.create_objects() def OnReshape(self, width, height): '''Reshape the OpenGL viewport based on the dimensions of the window.''' if not self.GLinitialized: self.OnInitGL() self.GLinitialized = True self.pmat=(GLdouble * 16)() self.mvmat=(GLdouble * 16)() glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(60., width / float(height), .1, 1000.) glMatrixMode(GL_MODELVIEW) glLoadIdentity() #pyglet stuff self.vpmat=(GLint * 4)(0,0,*list(self.GetClientSize())) glGetDoublev(GL_PROJECTION_MATRIX,self.pmat) glGetDoublev(GL_MODELVIEW_MATRIX,self.mvmat) #glMatrixMode(GL_PROJECTION) # Wrap text to the width of the window if self.GLinitialized: self.pygletcontext.set_current() self.update_object_resize() def OnDraw(self, *args, **kwargs): """Draw the window.""" #clear the context self.canvas.SetCurrent() self.pygletcontext.set_current() glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) #draw objects self.draw_objects() #update screen self.SwapBuffers() #========================================================================== # To be implemented by a sub class #========================================================================== def create_objects(self): '''create opengl objects when opengl is initialized''' pass def update_object_resize(self): '''called when the window recieves only if opengl is initialized''' pass def draw_objects(self): '''called in the middle of ondraw after the buffer has been cleared''' pass class stlview(object): def __init__(self, facets, batch): # Create the vertex and normal arrays. vertices = [] normals = [] for i in facets: for j in i[1]: vertices.extend(j) normals.extend(i[0]) # Create a list of triangle indices. indices = range(3*len(facets))#[[3*i,3*i+1,3*i+2] for i in xrange(len(facets))] #print indices[:10] self.vertex_list = batch.add_indexed(len(vertices)//3, GL_TRIANGLES, None,#group, indices, ('v3f/static', vertices), ('n3f/static', normals)) def delete(self): self.vertex_list.delete() def vdiff(v,o): return [x[0]-x[1] for x in zip(v,o)] class gcview(object): def __init__(self, lines, batch, w=0.5, h=0.5): # Create the vertex and normal arrays. vertices = [] normals = [] self.prev=[0.001,0.001,0.001,0.001] self.fline=1 self.vlists=[] self.layers={} t0=time.time() lines=[self.transform(i) for i in lines] lines=[i for i in lines if i is not None] print "transformed lines in %fs"%(time.time()-t0) t0=time.time() layertemp={} lasth=None counter=0 if len(lines)==0: return for i in lines: counter+=1 if i[0][2] not in layertemp: layertemp[i[0][2]]=[[],[]] if lasth is not None: self.layers[lasth]=pyglet.graphics.Batch() lt=layertemp[lasth][0] indices = range(len(layertemp[lasth][0])//3)#[[3*i,3*i+1,3*i+2] for i in xrange(len(facets))] self.vlists.append(self.layers[lasth].add_indexed(len(layertemp[lasth][0])//3, GL_TRIANGLES, None,#group, indices, ('v3f/static', layertemp[lasth][0]), ('n3f/static', layertemp[lasth][1]))) lasth=i[0][2] spoints,epoints,S,E=self.genline(i,h,w) verticestoadd=[[spoints[(j+1)%8],epoints[(j)%8],spoints[j],epoints[j],spoints[(j+1)%8],epoints[(j+1)%8]] for j in xrange(8)] normalstoadd=[map(vdiff,v,[S,E,S,E,S,E]) for v in verticestoadd] v1=[] map(v1.extend,verticestoadd) v2=[] map(v2.extend,v1) n1=[] map(n1.extend,normalstoadd) n2=[] map(n2.extend,n1) layertemp[i[0][2]][0]+=v2 vertices+=v2 layertemp[i[0][2]][1]+=n2 normals+=n2 print "appended lines in %fs"%(time.time()-t0) t0=time.time() # Create a list of triangle indices. indices = range(3*16*len(lines))#[[3*i,3*i+1,3*i+2] for i in xrange(len(facets))] self.vlists.append(batch.add_indexed(len(vertices)//3, GL_TRIANGLES, None,#group, indices, ('v3f/static', vertices), ('n3f/static', normals))) if lasth is not None: self.layers[lasth]=pyglet.graphics.Batch() indices = range(len(layertemp[lasth][0]))#[[3*i,3*i+1,3*i+2] for i in xrange(len(facets))] self.vlists.append(self.layers[lasth].add_indexed(len(layertemp[lasth][0])//3, GL_TRIANGLES, None,#group, indices, ('v3f/static', layertemp[lasth][0]), ('n3f/static', layertemp[lasth][1]))) def genline(self,i,h,w): S=i[0][:3] E=i[1][:3] v=map(lambda x,y:x-y,E,S) vlen=math.sqrt(float(sum(map(lambda a:a*a, v[:3])))) if vlen==0: vlen=0.01 sq2=math.sqrt(2.0)/2.0 htw=float(h)/w d=w/2.0 if i[1][3]==i[0][3]: d=0.05 points=[[d,0,0], [sq2*d,sq2*d,0], [0,d,0], [-sq2*d,sq2*d,0], [-d,0,0], [-sq2*d,-sq2*d,0], [0,-d,0], [sq2*d,-sq2*d,0] ] axis=stltool.cross([0,0,1],v) alen=math.sqrt(float(sum(map(lambda a:a*a, v[:3])))) if alen>0: axis=map(lambda m:m/alen,axis) angle=math.acos(v[2]/vlen) def vrot(v,axis,angle): kxv=stltool.cross(axis,v) kdv=sum(map(lambda x,y:x*y,axis,v)) return map(lambda x,y,z:x*math.cos(angle)+y*math.sin(angle)+z*kdv*(1.0-math.cos(angle)),v,kxv,axis) points=map(lambda x:vrot(x,axis,angle),points) points=map(lambda x:[x[0],x[1],htw*x[2]],points) def vadd(v,o): return map(sum,zip(v,o)) spoints=map(lambda x:vadd(S,x),points) epoints=map(lambda x:vadd(E,x),points) return spoints,epoints,S,E def transform(self,line): line=line.split(";")[0] cur=self.prev[:] if len(line)>0: if "G1" in line or "G0" in line or "G92" in line: if("X" in line): cur[0]=float(line.split("X")[1].split(" ")[0]) if("Y" in line): cur[1]=float(line.split("Y")[1].split(" ")[0]) if("Z" in line): cur[2]=float(line.split("Z")[1].split(" ")[0]) if("E" in line): cur[3]=float(line.split("E")[1].split(" ")[0]) if self.prev==cur: return None if self.fline or "G92" in line: self.prev=cur self.fline=0 return None else: r=[self.prev,cur] self.prev=cur return r def delete(self): for i in self.vlists: i.delete() self.vlists=[] def trackball(p1x, p1y, p2x, p2y, r): TRACKBALLSIZE=r #float a[3]; /* Axis of rotation */ #float phi; /* how much to rotate about axis */ #float p1[3], p2[3], d[3]; #float t; if (p1x == p2x and p1y == p2y): return [0.0,0.0,0.0,1.0] p1=[p1x,p1y,project_to_sphere(TRACKBALLSIZE,p1x,p1y)] p2=[p2x,p2y,project_to_sphere(TRACKBALLSIZE,p2x,p2y)] a=stltool.cross(p2,p1) d=map(lambda x,y:x-y,p1,p2) t = math.sqrt(sum(map(lambda x:x*x, d))) / (2.0*TRACKBALLSIZE) if (t > 1.0): t = 1.0 if (t < -1.0): t = -1.0 phi = 2.0 * math.asin(t) return axis_to_quat(a,phi) def vec(*args): return (GLfloat * len(args))(*args) def axis_to_quat(a,phi): #print a, phi lena=math.sqrt(sum(map(lambda x:x*x, a))) q=map(lambda x:x*(1/lena),a) q=map(lambda x:x*math.sin(phi/2.0),q) q.append(math.cos(phi/2.0)) return q def build_rotmatrix(q): m=(GLdouble * 16)() m[0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]) m[1] = 2.0 * (q[0] * q[1] - q[2] * q[3]); m[2] = 2.0 * (q[2] * q[0] + q[1] * q[3]); m[3] = 0.0; m[4] = 2.0 * (q[0] * q[1] + q[2] * q[3]); m[5]= 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]); m[6] = 2.0 * (q[1] * q[2] - q[0] * q[3]); m[7] = 0.0; m[8] = 2.0 * (q[2] * q[0] - q[1] * q[3]); m[9] = 2.0 * (q[1] * q[2] + q[0] * q[3]); m[10] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]); m[11] = 0.0; m[12] = 0.0; m[13] = 0.0; m[14] = 0.0; m[15] = 1.0; return m def project_to_sphere(r, x, y): d = math.sqrt(x*x + y*y) if (d < r * 0.70710678118654752440): return math.sqrt(r*r - d*d) else: t = r / 1.41421356237309504880 return t*t / d def mulquat(q1,rq): return [q1[3] * rq[0] + q1[0] * rq[3] + q1[1] * rq[2] - q1[2] * rq[1], q1[3] * rq[1] + q1[1] * rq[3] + q1[2] * rq[0] - q1[0] * rq[2], q1[3] * rq[2] + q1[2] * rq[3] + q1[0] * rq[1] - q1[1] * rq[0], q1[3] * rq[3] - q1[0] * rq[0] - q1[1] * rq[1] - q1[2] * rq[2]] class TestGlPanel(GLPanel): def __init__(self, parent, size,id=wx.ID_ANY,): super(TestGlPanel, self).__init__(parent, id, wx.DefaultPosition, size, 0) self.batches=[] self.rot=0 self.canvas.Bind(wx.EVT_MOUSE_EVENTS,self.move) self.canvas.Bind(wx.EVT_LEFT_DCLICK,self.double) self.initialized=1 self.canvas.Bind(wx.EVT_MOUSEWHEEL,self.wheel) self.parent=parent self.initpos=None self.dist=200 self.bedsize=[200,200] self.transv=[0, 0, -self.dist] self.basequat=[0,0,0,1] wx.CallAfter(self.forceresize) self.mousepos=[0,0] def double(self, event): p=event.GetPositionTuple() sz=self.GetClientSize() v=map(lambda m,w,b:b*m/w,p,sz,self.bedsize) v[1]=self.bedsize[1]-v[1] v+=[300] print v self.add_file("../prusa/metric-prusa/x-end-idler.stl",v) def forceresize(self): self.SetClientSize((self.GetClientSize()[0],self.GetClientSize()[1]+1)) self.SetClientSize((self.GetClientSize()[0],self.GetClientSize()[1]-1)) threading.Thread(target=self.update).start() self.initialized=0 def move_shape(self, delta): """moves shape (selected in l, which is list ListBox of shapes) by an offset specified in tuple delta. Positive numbers move to (rigt, down)""" name = self.parent.l.GetSelection() if name == wx.NOT_FOUND: return False name = self.parent.l.GetString(name) model = self.parent.models[name] model.offsets = [ model.offsets[0] + delta[0], model.offsets[1] + delta[1], model.offsets[2] ] self.Refresh() return True def move(self, event): """react to mouse actions: no mouse: show red mousedrop LMB: move active object, with shift rotate viewport RMB: nothing with shift move viewport """ if event.Dragging() and event.LeftIsDown(): if self.initpos==None: self.initpos=event.GetPositionTuple() else: if not event.ShiftDown(): currentpos = event.GetPositionTuple() delta = ( (currentpos[0] - self.initpos[0]), -(currentpos[1] - self.initpos[1]) ) self.move_shape(delta) self.initpos=None return #print self.initpos p1=self.initpos self.initpos=None p2=event.GetPositionTuple() sz=self.GetClientSize() p1x=(float(p1[0])-sz[0]/2)/(sz[0]/2) p1y=-(float(p1[1])-sz[1]/2)/(sz[1]/2) p2x=(float(p2[0])-sz[0]/2)/(sz[0]/2) p2y=-(float(p2[1])-sz[1]/2)/(sz[1]/2) #print p1x,p1y,p2x,p2y quat=trackball(p1x, p1y, p2x, p2y, -self.transv[2]/250.0) if self.rot: self.basequat=mulquat(self.basequat,quat) #else: glGetDoublev(GL_MODELVIEW_MATRIX,self.mvmat) #self.basequat=quatx mat=build_rotmatrix(self.basequat) glLoadIdentity() glTranslatef(self.transv[0],self.transv[1],0) glTranslatef(0,0,self.transv[2]) glMultMatrixd(mat) glGetDoublev(GL_MODELVIEW_MATRIX,self.mvmat) self.rot=1 elif event.ButtonUp(wx.MOUSE_BTN_LEFT): if self.initpos is not None: self.initpos=None elif event.ButtonUp(wx.MOUSE_BTN_RIGHT): if self.initpos is not None: self.initpos=None elif event.Dragging() and event.RightIsDown() and event.ShiftDown(): if self.initpos is None: self.initpos=event.GetPositionTuple() else: p1=self.initpos p2=event.GetPositionTuple() sz=self.GetClientSize() p1=list(p1) p2=list(p2) p1[1]*=-1 p2[1]*=-1 self.transv=map(lambda x,y,z,c:c-self.dist*(x-y)/z, list(p1)+[0], list(p2)+[0], list(sz)+[1], self.transv) glLoadIdentity() glTranslatef(self.transv[0],self.transv[1],0) glTranslatef(0,0,self.transv[2]) if(self.rot): glMultMatrixd(build_rotmatrix(self.basequat)) glGetDoublev(GL_MODELVIEW_MATRIX,self.mvmat) self.rot=1 self.initpos=None else: #mouse is moving without a button press p=event.GetPositionTuple() sz=self.GetClientSize() v=map(lambda m,w,b:b*m/w,p,sz,self.bedsize) v[1]=self.bedsize[1]-v[1] self.mousepos=v def rotate_shape(self, angle): """rotates acive shape positive angle is clockwise """ name = self.parent.l.GetSelection() if name == wx.NOT_FOUND: return False name = self.parent.l.GetString(name) model = self.parent.models[name] model.rot += angle def wheel(self,event): """react to mouse wheel actions: rotate object with shift zoom viewport """ z=event.GetWheelRotation() angle=10 if not event.ShiftDown(): i=self.parent.l.GetSelection() if i<0: try: self.parent.setlayerindex(z) except: pass return if z > 0: self.rotate_shape(angle/2) else: self.rotate_shape(-angle/2) return if z > 0: self.transv[2]+=angle else: self.transv[2]-=angle glLoadIdentity() glTranslatef(*self.transv) if(self.rot): glMultMatrixd(build_rotmatrix(self.basequat)) glGetDoublev(GL_MODELVIEW_MATRIX,self.mvmat) self.rot=1 def keypress(self, event): """gets keypress events and moves/rotates acive shape""" keycode = event.GetKeyCode() print keycode step = 5 angle = 18 if event.ControlDown(): step = 1 angle = 1 #h if keycode == 72: self.move_shape((-step, 0)) #l if keycode == 76: self.move_shape((step, 0)) #j if keycode == 75: self.move_shape((0, step)) #k if keycode == 74: self.move_shape((0, -step)) #[ if keycode == 91: self.rotate_shape(-angle) #] if keycode == 93: self.rotate_shape(angle) event.Skip() def update(self): while(1): dt=0.05 time.sleep(0.05) try: wx.CallAfter(self.Refresh) except: return def anim(self,obj): g=50*9.8 v=20 dt=0.05 basepos=obj.offsets[2] obj.offsets[2]+=obj.animoffset while obj.offsets[2]>-1: time.sleep(dt) obj.offsets[2]-=v*dt v+=g*dt if(obj.offsets[2]<0): obj.scale[2]*=1-3*dt #return v=v/4 while obj.offsets[2]0 and self.modelindex0: self.modelindex-=1 m.curlayer=mlk[self.modelindex] wx.CallAfter(self.SetTitle,"Gcode view, shift to move. Layer %d, Z=%f"%(self.modelindex,m.curlayer)) def main(): app = wx.App(redirect=False) frame = GCFrame(None, wx.ID_ANY, 'Gcode view, shift to move view, mousewheel to set layer', size=(400,400)) frame.addfile(list(open("carriage dump_export.gcode"))) #frame = wx.Frame(None, -1, "GL Window", size=(400,400)) #panel = TestGlPanel(frame) #frame.Show(True) #app.MainLoop() app.Destroy() if __name__=="__main__": import cProfile print cProfile.run("main()")