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