I want to write a script in Python which can generate facegroups in a STL as per the Face Normal value condition. For example, Provided is the snap of Stl, Different colour signifies the face group containing the triangular faces satisfying my given face normal threshold. Is there any simple way to do this in python?
Face Group STL
I'm sure there's a python library to load stl files, but I've always just written my own, since the file format is pretty simple (see the Wikipedia article for file format description).
Here is my code to read the stl file:
import numpy as np
import structdef Unique(inputList):""" Given an M x N list, this function gets the unique rows by treating allM Ntuples as single objects. This function also returns the indexingto convert the unique returned list back to the original non-unique list."""hashTable=dict()indexList=[]uniqueList=[]indx=0for ntuple in inputList:if not ntuple in hashTable:hashTable[ntuple]=indxindexList.append(indx)uniqueList.append(ntuple)indx+=1else:indexList.append(hashTable.get(ntuple)) return uniqueList, indexListdef IsBinarySTL(filename):try:with open(filename,'r') as f:test=f.readline()except UnicodeDecodeError:return Trueif len(test) < 5:return Trueelif test[0:5].lower() == 'solid':return False # ASCII STLelse:return Truedef ReadSTL(filename):""" Returns numpy arrays for vertices and facet indexing """def GetListFromASCII(filename):""" Returns vertex listing from ASCII STL file """outputList=[]with open(filename,'r') as f:lines=[line.split() for line in f.readlines()]for line in lines:if line[0] == 'vertex':outputList.append(tuple([float(x) for x in line[1:]]))return outputListdef GetListFromBinary(filename):""" Returns vertex listing from binary STL file """outputList=[]with open(filename,'rb') as f:f.seek(80) # skip headernFacets=struct.unpack('I',f.read(4))[0] # number of facets in piecefor i in range(nFacets):f.seek(12,1) # skip normaloutputList.append(struct.unpack('fff',f.read(12))) # append each vertex triple to list (each facet has 3 vertices)outputList.append(struct.unpack('fff',f.read(12))) outputList.append(struct.unpack('fff',f.read(12)))f.seek(2,1) # skip attributereturn outputListif IsBinarySTL(filename):vertexList = GetListFromBinary(filename)else:vertexList = GetListFromASCII(filename)coords, tempindxs = Unique(vertexList)indxs = list()templist = list()for i in range(len(tempindxs)):if (i > 0 ) and not (i % 3):indxs.append(templist)templist = list()templist.append(tempindxs[i])indxs.append(templist)return np.array(coords), np.array(indxs)
And here is code to compute the facet normals (assuming right-hand-rule)
def GetNormals(vertices, facets):""" Returns normals for each facet of mesh """u = vertices[facets[:,1],:] - vertices[facets[:,0],:]v = vertices[facets[:,2],:] - vertices[facets[:,0],:]normals = np.cross(u,v)norms = np.sqrt(np.sum(normals*normals, axis=1))return normals/norms[:, np.newaxis]
Finally, code to write out the stl file (assuming a list of attributes for each facet):
def WriteSTL(filename, vertices, facets, attributes, header):"""Writes vertices and facets to an stl file. Notes:1.) header can not be longer than 80 characters2.) length of attributes must be equal to length of facets3.) attributes must be integers"""nspaces = 80 - len(header)header += nspaces*'\0'nFacets = np.shape(facets)[0]stl = vertices[facets,:].tolist()with open(filename,'wb') as f: # binaryf.write(struct.pack('80s', header.encode('utf-8'))) # headerf.write(struct.pack('I',nFacets)) # number of facetsfor i in range(nFacets):f.write(struct.pack('fff',0,0,0)) # normals set to 0for j in range(3):f.write(struct.pack('fff',stl[i][j][0], stl[i][j][1], stl[i][j][2])) # 3 vertices per facet f.write(struct.pack("H", attributes[i])) # 2-byte attribute
Putting this all together, you can do something like the following:
if __name__ == "__main__":filename = "bunny.stl"vertices, facets = ReadSTL(filename) # parse stl filenormals = GetNormals(vertices, facets) # compute normals# Get some value related to normalsattributes = []for i in range(np.shape(normals)[0]):attributes.append(int(255*np.sum(normals[i])**2))# Write new stl fileWriteSTL("output.stl", vertices, facets, attributes, "stlheader")
this code snippet reads an stl file, computes the normals, and then assigns an attribute value based on the squared-sum of each normal (note that the attribute must be an integer).
The input and output of this script look like the following: