import struct normals = [] points = [] triangles = [] triangle_number = 0 def load_binary_stl(fp): ''' 二位元 STL 檔案格式如下: 檔案標頭共有 80 個字元(bytes), 內容通常省略, 但是內容不可使用 solid, 以免與文字檔案 STL 混淆 UINT8[80] – Header UINT32 – Number of triangles (I:佔 4 bytes 的 unsigned integer) foreach triangle REAL32[3] – Normal vector (f:每一座標分量為一佔 4 bytes 的 float, 共佔 12 bytes) REAL32[3] – Vertex 1 REAL32[3] – Vertex 2 REAL32[3] – Vertex 3 UINT16 – Attribute byte count (H:兩個 bytes 的 unsigned short, 表示 attribute byte count) end ''' # 已經在外部開檔 #fp=open(filename,'rb') header=fp.read(80) triangle_number = struct.unpack('I',fp.read(4))[0] #print(triangle_number) count=0 while True: try: p=fp.read(12) if len(p)==12: n=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] normals.append(n) l = len(points) #print(n) p=fp.read(12) if len(p)==12: p1=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] points.append(p1) #print(p1) p=fp.read(12) if len(p)==12: p2=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] points.append(p2) p=fp.read(12) if len(p)==12: p3=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] points.append(p3) triangles.append((l, l+1, l+2)) # 使用 count 來計算三角形平面個數 # triangle_number 為 STL 檔案中的三角形個數 count += 1 #print(count) # 在前面 12*4 個 bytes 的 normal 與三個點資料後, 為 # 一個 2 bytes 長的 unsigned short, 其值為零, 為 attribute fp.read(2) # 讀完所有三角平面後, 即跳出 while if count > triangle_number: break except EOFError: break #fp.close() def read_length(f): length = struct.unpack("@i", f.read(4)) return length[0] def read_header(f): f.seek(f.tell()+80) def write_as_ascii(outfilename): f = open(outfilename, "w") f.write ("solid "+outfilename+"\n") for n in range(len(triangles)): f.write ("facet normal {} {} {}\n".format(normals[n][0],normals[n][1],normals[n][2])) f.write ("outer loop\n") f.write ("vertex {} {} {}\n".format(points[triangles[n][0]][0],points[triangles[n][0]][1],points[triangles[n][0]][2])) f.write ("vertex {} {} {}\n".format(points[triangles[n][1]][0],points[triangles[n][1]][1],points[triangles[n][1]][2])) f.write ("vertex {} {} {}\n".format(points[triangles[n][2]][0],points[triangles[n][2]][1],points[triangles[n][2]][2])) f.write ("endloop\n") f.write ("endfacet\n") f.write ("endsolid "+outfilename+"\n") f.close() def main(): infilename = "binary1.stl" outfilename = "ascii1.stl" try: f = open(infilename, "rb") #read_header(f) #l = read_length(f) try: load_binary_stl(f) l = len(normals) except Exception as e: print("Exception",e) print(len(normals), len(points), len(triangles), l) write_as_ascii(outfilename) print("done") except Exception as e: print(e) if __name__ == '__main__': main()
其他相關程式:
建立 Binary STL 零件檔案:
#coding: utf-8 import struct class StLFacet: def __init__(self, normal, v1, v2, v3, att_bc=0): self.coords = [normal, v1, v2, v3] self.att_bc = att_bc class StL: def __init__(self, header): self.header = header self.facets = [] def add_facet(self, facet): self.facets.append(facet) def get_binary(self): # 原先 2.0 的版本 #out = ['%-80.80s' % self.header] # 改為 Python 3.0 格式 # 第一行標頭的格式 header = ['%-80.80s' % self.header][0] # 利用 bytes() 將標頭字串轉為二位元資料 out = [bytes(header,encoding="utf-8")] # 接著則計算三角形面的數量, 並以二位元長整數格式存檔 out.append(struct.pack('L',len(self.facets))) # 接著則依照法線向量與三個點座標的格式, 分別以浮點數格式進行資料附加 for f in self.facets: for coord in f.coords: out.append(struct.pack('3f', *coord)) # att_bc 則內定為 0 out.append(struct.pack('H', f.att_bc)) return b"".join(out) def test(): stl=StL('Header ...') stl.add_facet(StLFacet((0.,0.,1.),(0.,0.,0.),(1.,0.,0.),(0.,1.,0.))) stl.add_facet(StLFacet((0.,0.,1.),(1.,0.,0.),(1.,1.,0.),(0.,1.,0.))) # 第二個平面 stl.add_facet(StLFacet((0.,-1.,0.),(0.,0.,0.),(0.,0.,-1.),(1.,0.,-1.))) stl.add_facet(StLFacet((0.,-1.,0.),(0.,0.,0.),(1.,0.,-1.),(1.,0.,0.))) return stl.get_binary() # 指定存為 binary 格式檔案 stlfile = open("test.stl", "wb") stlcontent = test() stlfile.write(stlcontent)
PyGame STL viewer:
#coding: utf-8 # STL viewer 原始檔案來自 # University of Wuppertal - http://mbi-wiki.uni-wuppertal.de/wordpress/ # Modified by Uli Eggersmann # Binary STL 資料讀取原始作者 Oliver Marks - http://www.linux.com # 原始檔案僅讀取 Text STL 零件檔案 # 2011 Fall 由 KMOL 新增 Binary STL 零件檔案讀取 from visual import scene, color, materials, faces, points import os, struct #file ="ritzel.stl" file ="binary.stl" scene.width = 400 scene.height = 400 scene.background = color.white # black # 視窗標題取自 cvisual.pyd, 不可使用中文 scene.title = "STLViewer in VPython" print ("利用滑鼠右鍵旋轉") print ("滑鼠左右鍵同時按下後移動, 可以縮放畫面") # Read STL file, only use vertex-line with xyz coordinates list = [] #load stl file detects if the file is a text file or binary file def load_stl(filename): #read start of file to determine if its a binay stl file or a ascii stl file fp=open(filename,'rb') header=fp.read(80) filetype=header[0:5] # 這裡必須要能夠分辨二位元字串與文字字串 #print (type(filetype)) #print (filetype) fp.close() # for Python 3 if filetype==b'solid': # for Python 2 #if filetype=='solid': print ("讀取文字檔案格式:"+str(filename)) load_text_stl(filename) else: print ("讀取二位元檔案格式:"+str(filename,)) load_binary_stl(filename) #load binary stl file check wikipedia for the binary layout of the file #we use the struct library to read in and convert binary data into a format we can use def load_binary_stl(filename): ''' 二位元 STL 檔案格式如下: 檔案標頭共有 80 個字元(bytes), 內容通常省略, 但是內容不可使用 solid, 以免與文字檔案 STL 混淆 UINT8[80] – Header UINT32 – Number of triangles (I:佔 4 bytes 的 unsigned integer) foreach triangle REAL32[3] – Normal vector (f:每一座標分量為一佔 4 bytes 的 float, 共佔 12 bytes) REAL32[3] – Vertex 1 REAL32[3] – Vertex 2 REAL32[3] – Vertex 3 UINT16 – Attribute byte count (H:兩個 bytes 的 unsigned short, 表示 attribute byte count) end ''' global list fp=open(filename,'rb') header=fp.read(80) triangle_number = struct.unpack('I',fp.read(4))[0] count=0 while True: try: p=fp.read(12) if len(p)==12: n=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] p=fp.read(12) if len(p)==12: p1=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] list.append(p1) p=fp.read(12) if len(p)==12: p2=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] list.append(p2) p=fp.read(12) if len(p)==12: p3=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]] list.append(p3) # 使用 count 來計算三角形平面個數 # triangle_number 為 STL 檔案中的三角形個數 count += 1 # 在前面 12*4 個 bytes 的 normal 與三個點資料後, 為 # 一個 2 bytes 長的 unsigned short, 其值為零, 為 attribute fp.read(2) # 讀完所有三角平面後, 即跳出 while if count > triangle_number: break except EOFError: break fp.close() def load_text_stl(filename): global list for dataline in open(filename,"r").readlines(): if not dataline.strip(): # skip blank lines continue field = dataline.split() # split with no argument makes the right place! if field[0] == "vertex": list.append([float(x) for x in field[1:4]]) #print (list) #break #for x in field[1:4]: #print(x) load_stl(os.path.abspath('')+'/'+file) # Graphics model = faces(pos=list, color=(0.8,0.8,0.8), material=materials.plastic) # creates triangles # 請注意, 這裡並沒有使用 STL 檔案中的平面 normal, 而是利用 VPython make_normals() 產生 model.make_normals() # creates plane normals model.smooth(0.93) # smooths the edges # = AllepunkteSTL points (pos = list, size = 3, color = Color.Black) # generates points
PyGame 與 OpenGL 檢視 STL
#coding: utf8 # source: https://www.linux.com/community/blogs/133-general-linux/291889 import os import struct from OpenGL.GL import * from OpenGL.GLU import * import pygame from pygame.locals import * #class for a 3d point class createpoint: def __init__(self,p,c=(1,0,0)): self.point_size=0.5 self.color=c self.x=p[0] self.y=p[1] self.z=p[2] def glvertex(self): glVertex3f(self.x,self.y,self.z) #class for a 3d face on a model class createtriangle: points=None normal=None def __init__(self,p1,p2,p3,n=None): #3 points of the triangle self.points=createpoint(p1),createpoint(p2),createpoint(p3) #triangles normal self.normal=createpoint(self.calculate_normal(self.points[0],self.points[1],self.points[2]))#(0,1,0)# #calculate vector / edge def calculate_vector(self,p1,p2): return -p1.x+p2.x,-p1.y+p2.y,-p1.z+p2.z def calculate_normal(self,p1,p2,p3): a=self.calculate_vector(p3,p2) b=self.calculate_vector(p3,p1) #calculate the cross product returns a vector return self.cross_product(a,b) def cross_product(self,p1,p2): return (p1[1]*p2[2]-p2[1]*p1[2]) , (p1[2]*p2[0])-(p2[2]*p1[0]) , (p1[0]*p2[1])-(p2[0]*p1[1]) class loader: model=[] #return the faces of the triangles def get_triangles(self): if self.model: for face in self.model: yield face #draw the models faces def draw(self): glBegin(GL_TRIANGLES) for tri in self.get_triangles(): glNormal3f(tri.normal.x,tri.normal.y,tri.normal.z) glVertex3f(tri.points[0].x,tri.points[0].y,tri.points[0].z) glVertex3f(tri.points[1].x,tri.points[1].y,tri.points[1].z) glVertex3f(tri.points[2].x,tri.points[2].y,tri.points[2].z) glEnd() #load stl file detects if the file is a text file or binary file def load_stl(self,filename): #read start of file to determine if its a binay stl file or a ascii stl file fp=open(filename,'rb') h=fp.read(80) type=h[0:5] fp.close() if type=='solid': print ("reading text file"+str(filename)) self.load_text_stl(filename) else: print ("reading binary stl file "+str(filename,)) self.load_binary_stl(filename) #read text stl match keywords to grab the points to build the model def load_text_stl(self,filename): fp=open(filename,'r') for line in fp.readlines(): words=line.split() if len(words)>0: if words[0]=='solid': self.name=words[1] if words[0]=='facet': center=[0.0,0.0,0.0] triangle=[] normal=(eval(words[2]),eval(words[3]),eval(words[4])) if words[0]=='vertex': triangle.append((eval(words[1]),eval(words[2]),eval(words[3]))) if words[0]=='endloop': #make sure we got the correct number of values before storing if len(triangle)==3: self.model.append(createtriangle(triangle[0],triangle[1],triangle[2],normal)) fp.close() #load binary stl file check wikipedia for the binary layout of the file #we use the struct library to read in and convert binary data into a format we can use def load_binary_stl(self,filename): fp=open(filename,'rb') h=fp.read(80) l=struct.unpack('I',fp.read(4))[0] count=0 while True: try: p=fp.read(12) if len(p)==12: n=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0] p=fp.read(12) if len(p)==12: p1=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0] p=fp.read(12) if len(p)==12: p2=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0] p=fp.read(12) if len(p)==12: p3=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0] new_tri=(n,p1,p2,p3) if len(new_tri)==4: tri=createtriangle(p1,p2,p3,n) self.model.append(tri) count+=1 fp.read(2) if len(p)==0: break except EOFError: break fp.close() class draw_scene: def __init__(self,style=1): #create a model instance and self.model1=loader() #self.model1.load_stl(os.path.abspath('')+'/text.stl') self.model1.load_stl(os.path.abspath('')+'/cube.stl') self.init_shading() #solid model with a light / shading def init_shading(self): glShadeModel(GL_SMOOTH) glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepth(1.0) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) glDepthFunc(GL_LEQUAL) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) glEnable(GL_COLOR_MATERIAL) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glLight(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0)) glMatrixMode(GL_MODELVIEW) def resize(self, width, height): if height==0: height=1 glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45, 1.0*width/height, 0.1, 100.0) #gluLookAt(0.0,0.0,45.0,0,0,0,0,40.0,0) glMatrixMode(GL_MODELVIEW) glLoadIdentity() def init(self): glShadeModel(GL_SMOOTH) glClearColor(0.0, 0.0, 0.0, 0.0) glClearDepth(1.0) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) glDepthFunc(GL_LEQUAL) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) glEnable(GL_COLOR_MATERIAL) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glLight(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0)) glMatrixMode(GL_MODELVIEW) def draw(self): glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) glLoadIdentity() glTranslatef(0.0,-26.0, -100.0) self.model1.draw() #main program loop def main(): #initalize pygame pygame.init() pygame.display.set_mode((640,480), OPENGL|DOUBLEBUF) #setup the open gl scene scene=draw_scene() scene.resize(640,480) frames = 0 ticks = pygame.time.get_ticks() while 1: event = pygame.event.poll() if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): break #draw the scene scene.draw() pygame.display.flip() frames = frames+1 print ("fps: %d" % ((frames*1000)/(pygame.time.get_ticks()-ticks))) if __name__ == '__main__': main()
另一個 STL Writer:
#coding:utf-8 # source: http://code.activestate.com/recipes/578246-stl-writer/ import struct ASCII_FACET = """facet normal 0 0 0 outer loop vertex {face[0][0]:.4f} {face[0][1]:.4f} {face[0][2]:.4f} vertex {face[1][0]:.4f} {face[1][1]:.4f} {face[1][2]:.4f} vertex {face[2][0]:.4f} {face[2][1]:.4f} {face[2][2]:.4f} endloop endfacet """ BINARY_HEADER ="80sI" BINARY_FACET = "12fH" class ASCII_STL_Writer: """ Export 3D objects build of 3 or 4 vertices as ASCII STL file. """ def __init__(self, stream): self.fp = stream self._write_header() def _write_header(self): self.fp.write("solid python\n") def close(self): self.fp.write("endsolid python\n") def _write(self, face): self.fp.write(ASCII_FACET.format(face=face)) def _split(self, face): p1, p2, p3, p4 = face return (p1, p2, p3), (p3, p4, p1) def add_face(self, face): """ Add one face with 3 or 4 vertices. """ if len(face) == 4: face1, face2 = self._split(face) self._write(face1) self._write(face2) elif len(face) == 3: self._write(face) else: raise ValueError('only 3 or 4 vertices for each face') def add_faces(self, faces): """ Add many faces. """ for face in faces: self.add_face(face) class Binary_STL_Writer(ASCII_STL_Writer): """ Export 3D objects build of 3 or 4 vertices as binary STL file. """ def __init__(self, stream): self.counter = 0 super(Binary_STL_Writer, self).__init__(stream) def close(self): self._write_header() def _write_header(self): self.fp.seek(0) self.fp.write(struct.pack(BINARY_HEADER, b'Python Binary STL Writer', self.counter)) def _write(self, face): self.counter += 1 data = [ 0., 0., 0., face[0][0], face[0][1], face[0][2], face[1][0], face[1][1], face[1][2], face[2][0], face[2][1], face[2][2], 0 ] self.fp.write(struct.pack(BINARY_FACET, *data)) def example(): def get_cube(): # cube corner points s = 3. p1 = (0, 0, 0) p2 = (0, 0, s) p3 = (0, s, 0) p4 = (0, s, s) p5 = (s, 0, 0) p6 = (s, 0, s) p7 = (s, s, 0) p8 = (s, s, s) # define the 6 cube faces # faces just lists of 3 or 4 vertices return [ [p1, p5, p7, p3], [p1, p5, p6, p2], [p5, p7, p8, p6], [p7, p8, p4, p3], [p1, p3, p4, p2], [p2, p6, p8, p4], ] ''' for writing ASCII STL cube file with open('cube_ascii.stl', 'w') as fp: writer = ASCII_STL_Writer(fp) writer.add_faces(get_cube()) writer.close() ''' with open('cube_bin.stl', 'wb') as fp: writer = Binary_STL_Writer(fp) writer.add_faces(get_cube()) writer.close() if __name__ == '__main__': example()
No comments:
Post a Comment